Env setup

install.packages(c("patchwork"))
Installing patchwork [1.1.1] ...
    OK [copied local binary]
suppressPackageStartupMessages({
  library(tidyverse)
  library(MOFA2)
  library(Matrix)
  library(SingleCellExperiment)
  library(scran)
  library(glue)
  library(scater)
  library(patchwork)
  library(batchelor)
  library(rhdf5)
  # library(ggraph)
  }
  )

Define plotting utils

remove_x_axis <- function(){
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())  
}

remove_y_axis <- function(){
  theme(axis.text.y = element_blank(), axis.ticks.y = element_blank(), axis.title.y = element_blank())  
}

org_colors <- read_csv("~/Pan_fetal_immune/metadata/organ_colors.csv")
New names:
* `` -> ...1
Rows: 9 Columns: 3
── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (2): organ, color
dbl (1): ...1

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
org_colors <- setNames(org_colors$color, org_colors$organ)
figdir <- "~/mount/gdrive/Pan_fetal/Updates_and_presentations/figures/MOFA_analysis/"
if (!dir.exists(figdir)){ dir.create(figdir) }

Load pseudobulked data

split = "LYMPHOID"
indir <- glue("/nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_{split}_PBULK/")

matrix <- readMM(file = paste0(indir, "matrix.mtx.gz"))
coldata <- read.csv(file = paste0(indir, "metadata.csv.gz"))  %>%
  column_to_rownames("X")
rowdata <- read.csv(file = paste0(indir, "gene.csv.gz")) 

## Make SingleCellExperiment obj
sce <- SingleCellExperiment(list(logcounts = t(matrix)), colData = coldata)
rownames(sce) <- make.unique(rowdata$GeneName) 
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

Preprocessing

Filtering samples

anno_groups
$MYELOID
 [1] "DC3"                                 "DC2"                                 "DC1"                                
 [4] "PROLIFERATING_MACROPHAGE"            "EO/BASO/MAST"                        "CD14_MONO"                          
 [7] "CD14+_MACROPHAGE"                    "OLFML3+_MICROGLIA"                   "PROMONOCYTE_(PROLIFERATING)"        
[10] "CD16+_MACROPHAGE"                    "NEUTROPHIL"                          "PROMONOCYTE"                        
[13] "YS_MACROPHAGE"                       "BM_CD14_MONO"                        "KUPFFER_RP_MACROPHAGE"              
[16] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "YS_ERY_MACROPHAGE"                   "SPLENIC_MACROPHAGE"                 
[19] "OSTEOCLAST"                         

$`ERYTHROID CELLS`
[1] "EARLY_ERY"      "EARLY_MK"       "PROMYELOCYTE"   "ERY_MACROPHAGE" "MID_ERY"        "LATE_ERY"       "LATE_MK"       
[8] "YS_ERY"         "CYCLING_YS_ERY"

$`B CELLS`
 [1] "LARGE_PRE_B" "PRE_PRO_B"   "PRO_B"       "CYCLING_B"   "SMALL_PRE_B" "MATURE_B"    "B1"          "IMMATURE_B"  "LATE_PRO_B" 
[10] "PLASMA_B"   

$OTHER
 [1] "DOUBLET_IMMUNE_FIBROBLAST"       "LOW_Q_INCONSISTENT"              "DOUBLET_LYMPHOID_MACROPHAGE"    
 [4] "LOW_QUALITY"                     "HIGH_MITO"                       "HIGH_MITO_DC"                   
 [7] "DOUBLETS_FIBRO_ERY"              "DOUBLET_ENDOTHELIUM_ERYTHROCYTE" "DOUBLET_ERY_B"                  
[10] "LOW_QUALITY_MACROPHAGE"          "LOW_QUALITY_MID_ERY_(HIGH_RIBO)" "PLACENTAL_CONTAMINANTS"         
[13] "DOUBLET"                        

$PROGENITORS
[1] "MEMP"         "GMP"          "LMPP_ELP"     "HSC_MPP"      "MEP"          "CMP"          "CYCLING_MEMP" "CYCLING_MPP" 

$STROMA
 [1] "ENDOTHELIUM_CLUST11"                   "VSMC/PERICYTE"                         "ENDOTHELIUM_CLUST42"                  
 [4] "CYCLING_FIBROBLAST_CLUST15"            "SKIN_FIBROBLAST_CLUST1"                "PERIVASCULAR_MACROPHAGE"              
 [7] "MELANOCYTE"                            "MYOFIBROBLAST"                         "SKIN_FIBROBLAST_CLUST8"               
[10] "CYCLING_FIBROBLAST_CLUST17"            "KERATINOCYTE"                          "SKIN_FIBROBLAST_CLUST25"              
[13] "SKIN_FIBROBLAST_CLUST2"                "GLIAL"                                 "SKIN_FIBROBLAST_CLUST22"              
[16] "ENDOTHELIUM_CLUST9"                    "VSMC"                                  "ENDOTHELIUM_CLUST5"                   
[19] "MESOTHELIUM"                           "SPLENIC_FIBROBLAST_CLUST26"            "SPLENIC_FIBROBLAST_CLUST0"            
[22] "GUT_FIBROBLAST_CLUST4"                 "HEPATIC_VSMC/PERICYTE"                 "GUT_FIBROBLAST_CLUST27"               
[25] "SMOOTH_MUSCLE"                         "NEURON"                                "GUT_FIBROBLAST_CLUST6"                
[28] "HEPATOCYTE-LIKE"                       "YS_STROMA"                             "FIBROBLAST_CLUST12"                   
[31] "HEPATOCYTE_CLUST33"                    "HEPATOCYTE_CLUST16"                    "GUT_EPITHELIUM_CLUST32"               
[34] "CYCLING_EPITHELIUM"                    "GUT_EPITHELIUM_CLUST10"                "EC"                                   
[37] "DEVELOPING_NEPHRON_CLUST21"            "DEVELOPING_NEPHRON_CLUST38"            "ENTEROENDOCRINE_CLUST46"              
[40] "GUT_MYOFIBROBLAST"                     "FIBROBLAST_CLUST31"                    "INTERSTITIAL_CELLS_OF_CAJAL"          
[43] "KIDNEY_VSMC/PERICYTE"                  "MESENCHYMAL_LYMPHOID_TISSUE_ORGANISER" "ENTEROENDOCRINE_CLUST52"              
[46] "SKIN_FIBROBLAST_CLUST29"               "SKIN_FIBROBLAST_CLUST24"               "SKIN_FIBROBLAST_CLUST30"              
[49] "MUSCLE_SATELLITE"                      "OSTEOBLAST"                            "ENDOTHELIUM_CLUST45"                  
[52] "SKELETAL_MUSCLE"                       "FIBROBLAST_CLUST53"                    "CHONDROCYTE"                          

$`NK/T CELLS`
 [1] "CYCLING_T"   "CD4+T"       "CD8+T"       "TREG"        "NK"          "CYCLING_NK"  "NK_T"        "DP(P)_T"     "TH17"       
[10] "CD8AA"       "ABT(ENTRY)"  "DP(Q)_T"     "DN(P)_T"     "DN(early)_T" "DN(Q)_T"    

$PDC
[1] "pDC"

$ILC
[1] "ILC3"        "ILC2"        "CYCLING_ILC"
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

Technical effect correction

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i])
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

sce <- sce[which(rowSums(logcounts(sce)) > 0),]
sce
class: SingleCellExperiment 
dim: 28260 993 
metadata(0):
assays(1): logcounts
rownames(28260): TSPAN6 TNMD ... AP000646.1 AP006216.3
rowData names(0):
colnames(993): F45_SK_CD45P_FCAImmP7579224-F45-SK-CD4+T-12-5GEX F45_SK_CD45P_FCAImmP7579224-F45-SK-CD8+T-12-5GEX ...
  F50_SP_CD45P_FCAImmP7803020-F50-SP-IMMATURE_B-15-5GEX F30_TH_CD45N_FCAImmP7277565-F30-TH-ABT(ENTRY)-14-3GEX
colData names(7): Sample donor ... method n_cells
reducedDimNames(0):
altExpNames(0):

EDA with PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, 
              exprs_values = "logcounts", subset_row=all_hvgs)

# ## Variance explained
# percent.var <- attr(reducedDim(sce), "percentVar")
# plot(percent.var, log="y", xlab="PC", ylab="Variance explained (%)")
plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=10)

Minimize obvious technical effects (3GEX/5GEX, donor) using linear regression (following procedure from OSCA)

## Regress technical effects
design <- model.matrix(~donor+method,data=colData(sce))
residuals <- regressBatches(sce, assay.type = "logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

## Regress organ (soup effect)
design <- model.matrix(~organ,data=colData(sce)) ## Include organ term to capture soup
residuals <- regressBatches(sce, assay.type = "corrected_logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

Check regression has an effect repeating PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, exprs_values = "corrected_logcounts")

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=8)

Feature selection

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i], assay.type = "corrected_logcounts")
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

FA Model - Normal MOFA / only celltypes as groups

Make MOFA object (Use celltypes as grouping covariate)

mofa <- create_mofa_from_SingleCellExperiment(sce[all_hvgs,], assay = "corrected_logcounts", 
                                              groups = "anno_lvl_2_final_clean", extract_metadata = TRUE)
Error in create_mofa_from_SingleCellExperiment(sce[all_hvgs, ], assay = "corrected_logcounts",  : 
  unused argument (extract_metadata = TRUE)
sessionInfo()
R version 4.0.4 (2021-02-15)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.1 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

locale:
 [1] LC_CTYPE=en_US.utf8       LC_NUMERIC=C              LC_TIME=en_US.utf8        LC_COLLATE=en_US.utf8     LC_MONETARY=en_US.utf8   
 [6] LC_MESSAGES=en_US.utf8    LC_PAPER=en_US.utf8       LC_NAME=C                 LC_ADDRESS=C              LC_TELEPHONE=C           
[11] LC_MEASUREMENT=en_US.utf8 LC_IDENTIFICATION=C      

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] rhdf5_2.34.0    glue_1.4.2      Matrix_1.3-2    MOFA2_1.0.1     forcats_0.5.1   stringr_1.4.0   dplyr_1.0.7     purrr_0.3.4    
 [9] readr_2.0.0     tidyr_1.1.3     tibble_3.1.3    ggplot2_3.3.5   tidyverse_1.3.1

loaded via a namespace (and not attached):
 [1] MatrixGenerics_1.2.1 httr_1.4.2           jsonlite_1.7.2       modelr_0.1.8         assertthat_0.2.1     BiocManager_1.30.16 
 [7] stats4_4.0.4         renv_0.12.5          cellranger_1.1.0     ggrepel_0.9.1        corrplot_0.90        pillar_1.6.2        
[13] backports_1.2.1      lattice_0.20-41      reticulate_1.20      RColorBrewer_1.1-2   rvest_1.0.1          colorspace_2.0-2    
[19] plyr_1.8.6           cowplot_1.1.1        pkgconfig_2.0.3      pheatmap_1.0.12      broom_0.7.9          haven_2.4.3         
[25] scales_1.1.1         HDF5Array_1.18.1     Rtsne_0.15           tzdb_0.1.2           generics_0.1.0       IRanges_2.24.1      
[31] ellipsis_0.3.2       withr_2.4.2          BiocGenerics_0.36.1  cli_3.0.1            magrittr_2.0.1       crayon_1.4.1        
[37] readxl_1.3.1         fs_1.5.0             fansi_0.5.0          xml2_1.3.2           tools_4.0.4          hms_1.1.0           
[43] lifecycle_1.0.0      matrixStats_0.60.0   basilisk.utils_1.2.2 Rhdf5lib_1.12.1      S4Vectors_0.28.1     munsell_0.5.0       
[49] reprex_2.0.1         DelayedArray_0.16.3  compiler_4.0.4       rlang_0.4.11         grid_4.0.4           rhdf5filters_1.2.1  
[55] rstudioapi_0.13      rappdirs_0.3.3       basilisk_1.2.1       gtable_0.3.0         DBI_1.1.1            reshape2_1.4.4      
[61] R6_2.5.0             lubridate_1.7.10     knitr_1.33           uwot_0.1.10          utf8_1.2.2           filelock_1.0.2      
[67] stringi_1.7.3        parallel_4.0.4       Rcpp_1.0.7           vctrs_0.3.8          png_0.1-7            dbplyr_2.1.1        
[73] tidyselect_1.1.1     xfun_0.25           

Prepare 4 training


data_opts <- get_default_data_options(object)
data_opts$use_float32 <- TRUE
data_opts$center_groups <- FALSE
object@data_options <- data_opts

model_opts <- get_default_model_options(object)
model_opts$num_factors <- 30
# model_opts$ard_factors <- FALSE

train_opts <- get_default_training_options(object)
train_opts$seed <- 2020
train_opts$convergence_mode <- "medium" # use "fast" for faster training
train_opts$stochastic <- FALSE

# mefisto_opts <- get_default_mefisto_options(object)
# mefisto_opts$warping <- FALSE
# mefisto_opts$sparseGP <- TRUE

object <- prepare_mofa(
  object = object,
  data_options = data_opts,
  model_options = model_opts,
  training_options = train_opts
) 

# Multi-group mode requested.

This is an advanced option, if this is the first time that you are running MOFA, we suggest that you try do some exploration first without specifying groups. Two important remarks:

 - The aim of the multi-group framework is to identify the sources of variability *within* the groups. If your aim is to find a factor that 'separates' the groups, you DO NOT want to use the multi-group framework. Please see the FAQ on the MOFA2 webpage.

 - It is important to account for the group effect before selecting highly variable features (HVFs). We suggest that either you calculate HVFs per group and then take the union, or regress out the group effect before HVF selection
Checking data options...
Due to string size limitations in the HDF5 format, sample names will be trimmed to less than 50 charactersChecking training options...
Checking model options...
object
Untrained MOFA model with the following characteristics: 
 Number of views: 1 
 Views names: corrected_logcounts 
 Number of features (per view): 7300 
 Number of groups: 23 
 Groups names: ABT(ENTRY) B1 CD4+T CD8+T CD8AA CYCLING_MPP CYCLING_NK CYCLING_T HSC_MPP ILC3 IMMATURE_B LARGE_PRE_B LATE_PRO_B LMPP_ELP MATURE_B MEMP NK NK_T PRE_PRO_B PRO_B SMALL_PRE_B TH17 TREG 
 Number of samples (per group): 26 32 63 55 24 28 61 38 32 62 29 54 32 10 55 26 87 52 40 50 46 43 48 
 

Train

Wrapped in run_mofa.R

outfile <- glue('{indir}{split}_mofa_model_oneview_organCorrected.hdf5')
mofa_trained <- run_mofa(object, outfile = outfile)
Warning: Output file /nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_LYMPHOID_PBULK/LYMPHOID_mofa_model_oneview_organCorrected.hdf5 already exists, it will be replaced
Connecting to the mofapy2 python package using reticulate (use_basilisk = FALSE)... 
    Please make sure to manually specify the right python binary when loading R with reticulate::use_python(..., force=TRUE) or the right conda environment with reticulate::use_condaenv(..., force=TRUE)
    If you prefer to let us automatically install a conda environment with 'mofapy2' installed using the 'basilisk' package, please use the argument 'use_basilisk = TRUE'

        #########################################################
        ###           __  __  ____  ______                    ### 
        ###          |  \/  |/ __ \|  ____/\    _             ### 
        ###          | \  / | |  | | |__ /  \ _| |_           ### 
        ###          | |\/| | |  | |  __/ /\ \_   _|          ###
        ###          | |  | | |__| | | / ____ \|_|            ###
        ###          |_|  |_|\____/|_|/_/    \_\              ###
        ###                                                   ### 
        ######################################################### 
       
 
        
use_float32 set to True: replacing float64 arrays by float32 arrays to speed up computations...

Successfully loaded view='corrected_logcounts' group='ABT(ENTRY)' with N=26 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='B1' with N=32 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CD4+T' with N=63 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CD8+T' with N=55 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CD8AA' with N=24 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CYCLING_MPP' with N=28 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CYCLING_NK' with N=61 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CYCLING_T' with N=38 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='HSC_MPP' with N=32 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='ILC3' with N=62 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='IMMATURE_B' with N=29 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='LARGE_PRE_B' with N=54 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='LATE_PRO_B' with N=32 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='LMPP_ELP' with N=10 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='MATURE_B' with N=55 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='MEMP' with N=26 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='NK' with N=87 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='NK_T' with N=52 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='PRE_PRO_B' with N=40 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='PRO_B' with N=50 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='SMALL_PRE_B' with N=46 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='TH17' with N=43 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='TREG' with N=48 samples and D=7300 features...


Model options:
- Automatic Relevance Determination prior on the factors: True
- Automatic Relevance Determination prior on the weights: True
- Spike-and-slab prior on the factors: False
- Spike-and-slab prior on the weights: True
Likelihoods:
- View 0 (corrected_logcounts): gaussian



Warning: some group(s) have less than 15 samples, MOFA won't be able to learn meaningful factors for these group(s)...



######################################
## Training the model with seed 2020 ##
######################################


ELBO before training: -132488757.08 

Iteration 1: time=10.91, ELBO=297780.79, deltaELBO=132786537.870 (100.22475929%), Factors=30
Iteration 2: time=11.01, Factors=30
Iteration 3: time=11.05, Factors=30
Iteration 4: time=10.97, Factors=30
Iteration 5: time=10.95, Factors=30
Iteration 6: time=11.00, ELBO=2908121.07, deltaELBO=2610340.279 (1.97023531%), Factors=30
Iteration 7: time=10.82, Factors=30
Iteration 8: time=10.70, Factors=30
Iteration 9: time=10.70, Factors=30
Iteration 10: time=10.80, Factors=30
Iteration 11: time=10.69, ELBO=3064916.38, deltaELBO=156795.305 (0.11834612%), Factors=30
Iteration 12: time=10.86, Factors=30
Iteration 13: time=10.77, Factors=30
Iteration 14: time=10.67, Factors=30
Iteration 15: time=11.03, Factors=30
Iteration 16: time=11.07, ELBO=3107186.46, deltaELBO=42270.084 (0.03190466%), Factors=30
Iteration 17: time=10.80, Factors=30
Iteration 18: time=11.07, Factors=30
Iteration 19: time=11.09, Factors=30
Iteration 20: time=10.95, Factors=30
Iteration 21: time=11.05, ELBO=3126530.89, deltaELBO=19344.434 (0.01460081%), Factors=30
Iteration 22: time=11.07, Factors=30
Iteration 23: time=11.02, Factors=30
Iteration 24: time=11.01, Factors=30
Iteration 25: time=11.02, Factors=30
Iteration 26: time=11.03, ELBO=3145472.08, deltaELBO=18941.185 (0.01429645%), Factors=30
Iteration 27: time=10.66, Factors=30
Iteration 28: time=10.77, Factors=30
Iteration 29: time=10.60, Factors=30
Iteration 30: time=10.82, Factors=30
Iteration 31: time=10.81, ELBO=3158977.22, deltaELBO=13505.138 (0.01019342%), Factors=30
Iteration 32: time=10.83, Factors=30
Iteration 33: time=10.76, Factors=30
Iteration 34: time=10.68, Factors=30
Iteration 35: time=10.82, Factors=30
Iteration 36: time=10.76, ELBO=3161387.65, deltaELBO=2410.430 (0.00181935%), Factors=30
Iteration 37: time=10.74, Factors=30
Iteration 38: time=10.87, Factors=30
Iteration 39: time=10.59, Factors=30
Iteration 40: time=10.80, Factors=30
Iteration 41: time=11.00, ELBO=3192915.90, deltaELBO=31528.249 (0.02379692%), Factors=30
Iteration 42: time=11.02, Factors=30
Iteration 43: time=10.59, Factors=30
Iteration 44: time=10.87, Factors=30
Iteration 45: time=10.95, Factors=30
Iteration 46: time=10.95, ELBO=3285178.21, deltaELBO=92262.317 (0.06963785%), Factors=30
Iteration 47: time=10.82, Factors=30
Iteration 48: time=11.08, Factors=30
Iteration 49: time=10.97, Factors=30
Iteration 50: time=10.82, Factors=30
Iteration 51: time=10.65, ELBO=3482792.60, deltaELBO=197614.387 (0.14915559%), Factors=30
Iteration 52: time=10.75, Factors=30
Iteration 53: time=11.60, Factors=30
Iteration 54: time=12.39, Factors=30
Iteration 55: time=19.86, Factors=30
Iteration 56: time=41.79, ELBO=3502687.97, deltaELBO=19895.369 (0.01501665%), Factors=30
Iteration 57: time=119.68, Factors=30
Iteration 58: time=102.59, Factors=30
Iteration 59: time=19.51, Factors=30
Iteration 60: time=10.35, Factors=30
Iteration 61: time=13.20, ELBO=3508492.11, deltaELBO=5804.146 (0.00438086%), Factors=30
Iteration 62: time=37.62, Factors=30
Iteration 63: time=74.94, Factors=30
Iteration 64: time=75.59, Factors=30
Iteration 65: time=39.72, Factors=30
Iteration 66: time=11.14, ELBO=3511429.39, deltaELBO=2937.276 (0.00221700%), Factors=30
Iteration 67: time=10.97, Factors=30
Iteration 68: time=10.75, Factors=30
Iteration 69: time=10.74, Factors=30
Iteration 70: time=10.66, Factors=30
Iteration 71: time=10.69, ELBO=3513329.29, deltaELBO=1899.897 (0.00143401%), Factors=30
Iteration 72: time=10.65, Factors=30
Iteration 73: time=10.83, Factors=30
Iteration 74: time=10.55, Factors=30
Iteration 75: time=10.67, Factors=30
Iteration 76: time=10.52, ELBO=3514696.51, deltaELBO=1367.217 (0.00103195%), Factors=30
Iteration 77: time=10.65, Factors=30
Iteration 78: time=10.71, Factors=30
Iteration 79: time=10.82, Factors=30
Iteration 80: time=10.77, Factors=30
Iteration 81: time=10.91, ELBO=3515690.31, deltaELBO=993.803 (0.00075010%), Factors=30
Iteration 82: time=10.84, Factors=30
Iteration 83: time=10.83, Factors=30
Iteration 84: time=10.80, Factors=30
Iteration 85: time=10.85, Factors=30
Iteration 86: time=10.95, ELBO=3516432.18, deltaELBO=741.873 (0.00055995%), Factors=30
Iteration 87: time=10.86, Factors=30
Iteration 88: time=10.90, Factors=30
Iteration 89: time=10.77, Factors=30
Iteration 90: time=10.70, Factors=30
Iteration 91: time=10.89, ELBO=3517017.27, deltaELBO=585.086 (0.00044161%), Factors=30
Iteration 92: time=10.74, Factors=30
Iteration 93: time=10.80, Factors=30
Iteration 94: time=10.42, Factors=30
Iteration 95: time=10.34, Factors=30
Iteration 96: time=10.52, ELBO=3517487.35, deltaELBO=470.086 (0.00035481%), Factors=30
Iteration 97: time=10.48, Factors=30
Iteration 98: time=10.64, Factors=30
Iteration 99: time=10.69, Factors=30
Iteration 100: time=10.42, Factors=30
Iteration 101: time=10.42, ELBO=3517868.74, deltaELBO=381.383 (0.00028786%), Factors=30
Iteration 102: time=10.39, Factors=30
Iteration 103: time=10.41, Factors=30
Iteration 104: time=10.43, Factors=30
Iteration 105: time=10.20, Factors=30
Iteration 106: time=10.37, ELBO=3518189.35, deltaELBO=320.618 (0.00024200%), Factors=30
Iteration 107: time=10.19, Factors=30
Iteration 108: time=10.51, Factors=30
Iteration 109: time=10.40, Factors=30
Iteration 110: time=10.71, Factors=30
Iteration 111: time=10.72, ELBO=3518481.19, deltaELBO=291.833 (0.00022027%), Factors=30
Iteration 112: time=10.58, Factors=30
Iteration 113: time=10.62, Factors=30
Iteration 114: time=10.55, Factors=30
Iteration 115: time=10.76, Factors=30
Iteration 116: time=10.40, ELBO=3518755.40, deltaELBO=274.218 (0.00020697%), Factors=30
Iteration 117: time=10.46, Factors=30
Iteration 118: time=10.47, Factors=30
Iteration 119: time=10.38, Factors=30
Iteration 120: time=10.36, Factors=30
Iteration 121: time=10.34, ELBO=3519004.66, deltaELBO=249.255 (0.00018813%), Factors=30
Iteration 122: time=10.34, Factors=30
Iteration 123: time=10.38, Factors=30
Iteration 124: time=10.21, Factors=30
Iteration 125: time=10.25, Factors=30
Iteration 126: time=10.23, ELBO=3519230.34, deltaELBO=225.680 (0.00017034%), Factors=30
Iteration 127: time=10.22, Factors=30
Iteration 128: time=10.34, Factors=30
Iteration 129: time=10.55, Factors=30
Iteration 130: time=10.66, Factors=30
Iteration 131: time=10.72, ELBO=3519455.12, deltaELBO=224.777 (0.00016966%), Factors=30
Iteration 132: time=10.69, Factors=30
Iteration 133: time=10.58, Factors=30
Iteration 134: time=10.55, Factors=30
Iteration 135: time=11.06, Factors=30
Iteration 136: time=10.72, ELBO=3519672.23, deltaELBO=217.114 (0.00016387%), Factors=30
Iteration 137: time=10.53, Factors=30
Iteration 138: time=10.68, Factors=30
Iteration 139: time=10.47, Factors=30
Iteration 140: time=10.36, Factors=30
Iteration 141: time=10.61, ELBO=3519885.70, deltaELBO=213.475 (0.00016113%), Factors=30
Iteration 142: time=10.64, Factors=30
Iteration 143: time=10.38, Factors=30
Iteration 144: time=10.35, Factors=30
Iteration 145: time=10.29, Factors=30
Iteration 146: time=10.41, ELBO=3520095.28, deltaELBO=209.580 (0.00015819%), Factors=30
Iteration 147: time=10.49, Factors=30
Iteration 148: time=10.45, Factors=30
Iteration 149: time=10.62, Factors=30
Iteration 150: time=10.42, Factors=30
Iteration 151: time=10.47, ELBO=3520303.13, deltaELBO=207.844 (0.00015688%), Factors=30
Iteration 152: time=10.44, Factors=30
Iteration 153: time=10.27, Factors=30
Iteration 154: time=10.17, Factors=30
Iteration 155: time=10.00, Factors=30
Iteration 156: time=10.29, ELBO=3520517.97, deltaELBO=214.838 (0.00016216%), Factors=30
Iteration 157: time=10.36, Factors=30
Iteration 158: time=10.39, Factors=30
Iteration 159: time=10.17, Factors=30
Iteration 160: time=10.42, Factors=30
Iteration 161: time=10.61, ELBO=3520733.62, deltaELBO=215.657 (0.00016277%), Factors=30
Iteration 162: time=10.45, Factors=30
Iteration 163: time=10.54, Factors=30
Iteration 164: time=10.34, Factors=30
Iteration 165: time=10.41, Factors=30
Iteration 166: time=10.34, ELBO=3520962.54, deltaELBO=228.911 (0.00017278%), Factors=30
Iteration 167: time=10.39, Factors=30
Iteration 168: time=10.35, Factors=30
Iteration 169: time=10.52, Factors=30
Iteration 170: time=10.59, Factors=30
Iteration 171: time=10.50, ELBO=3521187.99, deltaELBO=225.453 (0.00017017%), Factors=30
Iteration 172: time=10.28, Factors=30
Iteration 173: time=10.04, Factors=30
Iteration 174: time=10.15, Factors=30
Iteration 175: time=10.20, Factors=30
Iteration 176: time=10.07, ELBO=3521417.84, deltaELBO=229.853 (0.00017349%), Factors=30
Iteration 177: time=10.47, Factors=30
Iteration 178: time=10.57, Factors=30
Iteration 179: time=10.58, Factors=30
Iteration 180: time=10.52, Factors=30
Iteration 181: time=10.58, ELBO=3521649.14, deltaELBO=231.295 (0.00017458%), Factors=30
Iteration 182: time=10.55, Factors=30
Iteration 183: time=10.52, Factors=30
Iteration 184: time=10.22, Factors=30
Iteration 185: time=10.33, Factors=30
Iteration 186: time=10.51, ELBO=3521884.19, deltaELBO=235.055 (0.00017741%), Factors=30
Iteration 187: time=10.52, Factors=30
Iteration 188: time=10.28, Factors=30
Iteration 189: time=10.74, Factors=30
Iteration 190: time=10.70, Factors=30
Iteration 191: time=10.61, ELBO=3522087.38, deltaELBO=203.188 (0.00015336%), Factors=30
Iteration 192: time=10.65, Factors=30
Iteration 193: time=10.46, Factors=30
Iteration 194: time=10.35, Factors=30
Iteration 195: time=10.38, Factors=30
Iteration 196: time=10.47, ELBO=3522249.39, deltaELBO=162.006 (0.00012228%), Factors=30
Iteration 197: time=10.40, Factors=30
Iteration 198: time=10.47, Factors=30
Iteration 199: time=10.51, Factors=30
Iteration 200: time=10.60, Factors=30
Iteration 201: time=10.65, ELBO=3522388.87, deltaELBO=139.489 (0.00010528%), Factors=30
Iteration 202: time=10.53, Factors=30
Iteration 203: time=10.54, Factors=30
Iteration 204: time=10.51, Factors=30
Iteration 205: time=10.55, Factors=30
Iteration 206: time=10.65, ELBO=3522510.83, deltaELBO=121.954 (0.00009205%), Factors=30
Iteration 207: time=10.67, Factors=30
Iteration 208: time=10.70, Factors=30
Iteration 209: time=10.68, Factors=30
Iteration 210: time=10.51, Factors=30
Iteration 211: time=10.60, ELBO=3522625.47, deltaELBO=114.645 (0.00008653%), Factors=30
Iteration 212: time=10.78, Factors=30
Iteration 213: time=11.08, Factors=30
Iteration 214: time=10.73, Factors=30
Iteration 215: time=10.64, Factors=30
Iteration 216: time=10.69, ELBO=3522740.24, deltaELBO=114.764 (0.00008662%), Factors=30
Iteration 217: time=10.66, Factors=30
Iteration 218: time=10.60, Factors=30
Iteration 219: time=10.58, Factors=30
Iteration 220: time=10.53, Factors=30
Iteration 221: time=10.79, ELBO=3522844.27, deltaELBO=104.034 (0.00007852%), Factors=30
Iteration 222: time=10.71, Factors=30
Iteration 223: time=10.73, Factors=30
Iteration 224: time=10.63, Factors=30
Iteration 225: time=10.65, Factors=30
Iteration 226: time=10.58, ELBO=3522950.33, deltaELBO=106.061 (0.00008005%), Factors=30
Iteration 227: time=10.62, Factors=30
Iteration 228: time=10.53, Factors=30
Iteration 229: time=10.62, Factors=30
Iteration 230: time=10.61, Factors=30
Iteration 231: time=10.69, ELBO=3523051.08, deltaELBO=100.746 (0.00007604%), Factors=30
Iteration 232: time=10.55, Factors=30
Iteration 233: time=10.60, Factors=30
Iteration 234: time=10.55, Factors=30
Iteration 235: time=10.70, Factors=30
Iteration 236: time=10.78, ELBO=3523147.03, deltaELBO=95.951 (0.00007242%), Factors=30
Iteration 237: time=10.70, Factors=30
Iteration 238: time=10.51, Factors=30
Iteration 239: time=10.56, Factors=30
Iteration 240: time=10.53, Factors=30
Iteration 241: time=10.52, ELBO=3523240.55, deltaELBO=93.521 (0.00007059%), Factors=30
Iteration 242: time=10.56, Factors=30
Iteration 243: time=10.58, Factors=30
Iteration 244: time=10.80, Factors=30
Iteration 245: time=10.48, Factors=30
Iteration 246: time=10.65, ELBO=3523339.09, deltaELBO=98.544 (0.00007438%), Factors=30
Iteration 247: time=10.50, Factors=30
Iteration 248: time=10.52, Factors=30
Iteration 249: time=10.75, Factors=30
Iteration 250: time=10.79, Factors=30
Iteration 251: time=10.79, ELBO=3523433.87, deltaELBO=94.778 (0.00007154%), Factors=30
Iteration 252: time=10.62, Factors=30
Iteration 253: time=10.58, Factors=30
Iteration 254: time=10.55, Factors=30
Iteration 255: time=10.62, Factors=30
Iteration 256: time=10.50, ELBO=3523517.86, deltaELBO=83.987 (0.00006339%), Factors=30
Iteration 257: time=10.59, Factors=30
Iteration 258: time=10.49, Factors=30
Iteration 259: time=10.29, Factors=30
Iteration 260: time=10.57, Factors=30
Iteration 261: time=10.57, ELBO=3523587.97, deltaELBO=70.112 (0.00005292%), Factors=30
Iteration 262: time=10.75, Factors=30
Iteration 263: time=10.73, Factors=30
Iteration 264: time=10.57, Factors=30
Iteration 265: time=10.46, Factors=30
Iteration 266: time=10.62, ELBO=3523656.07, deltaELBO=68.095 (0.00005140%), Factors=30
Iteration 267: time=10.70, Factors=30
Iteration 268: time=10.76, Factors=30
Iteration 269: time=10.73, Factors=30
Iteration 270: time=10.67, Factors=30
Iteration 271: time=10.84, ELBO=3523726.93, deltaELBO=70.859 (0.00005348%), Factors=30
Iteration 272: time=10.42, Factors=30
Iteration 273: time=10.10, Factors=30
Iteration 274: time=10.24, Factors=30
Iteration 275: time=10.39, Factors=30
Iteration 276: time=10.56, ELBO=3523794.04, deltaELBO=67.113 (0.00005066%), Factors=30
Iteration 277: time=10.57, Factors=30
Iteration 278: time=10.63, Factors=30
Iteration 279: time=10.59, Factors=30
Iteration 280: time=10.55, Factors=30
Iteration 281: time=10.59, ELBO=3523861.29, deltaELBO=67.255 (0.00005076%), Factors=30
Iteration 282: time=10.57, Factors=30
Iteration 283: time=10.61, Factors=30
Iteration 284: time=10.50, Factors=30
Iteration 285: time=10.60, Factors=30
Iteration 286: time=10.65, ELBO=3523930.38, deltaELBO=69.090 (0.00005215%), Factors=30
Iteration 287: time=10.55, Factors=30
Iteration 288: time=10.34, Factors=30
Iteration 289: time=10.69, Factors=30
Iteration 290: time=10.50, Factors=30
Iteration 291: time=10.47, ELBO=3523995.62, deltaELBO=65.239 (0.00004924%), Factors=30
Iteration 292: time=10.39, Factors=30
Iteration 293: time=10.44, Factors=30
Iteration 294: time=10.66, Factors=30
Iteration 295: time=10.79, Factors=30
Iteration 296: time=10.74, ELBO=3524060.45, deltaELBO=64.828 (0.00004893%), Factors=30

Converged!



#######################
## Training finished ##
#######################


Warning: Output file /nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_LYMPHOID_PBULK/LYMPHOID_mofa_model_oneview_organCorrected.hdf5 already exists, it will be replaced
Saving model in /nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_LYMPHOID_PBULK/LYMPHOID_mofa_model_oneview_organCorrected.hdf5...
10 factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)
Error in .quality_control(object, verbose = verbose) : 
  !duplicated(unlist(samples_names(object))) are not all TRUE
### Tweaking the MOFA2 loading function because the quality control complains
load_model <- function(file, sort_factors = TRUE, on_disk = FALSE, load_data = TRUE,
                       remove_outliers = FALSE, remove_inactive_factors = TRUE, verbose = FALSE,
                       load_interpol_Z = FALSE) {

  # Create new MOFAodel object
  object <- new("MOFA")
  object@status <- "trained"
  
  # Set on_disk option
  if (on_disk) { 
    object@on_disk <- TRUE 
  } else { 
      object@on_disk <- FALSE 
  }
  
  # Get groups and data set names from the hdf5 file object
  h5ls.out <- h5ls(file, datasetinfo = FALSE)
  
  ########################
  ## Load training data ##
  ########################

  # Load names
  if ("views" %in% h5ls.out$name) {
    view_names <- as.character( h5read(file, "views")[[1]] )
    group_names <- as.character( h5read(file, "groups")[[1]] )
    feature_names <- h5read(file, "features")[view_names]
    sample_names  <- h5read(file, "samples")[group_names] 
  } else {  # for old models
    feature_names <- h5read(file, "features")
    sample_names  <- h5read(file, "samples")
    view_names <- names(feature_names)
    group_names <- names(sample_names)
    h5ls.out <- h5ls.out[grep("variance_explained", h5ls.out$name, invert = TRUE),]
  }
  if("covariates" %in%  h5ls.out$name){
    covariate_names <- as.character( h5read(file, "covariates")[[1]])
  } else {
    covariate_names <- NULL
  }

  # Load training data (as nested list of matrices)
  data <- list(); intercepts <- list()
  if (load_data && "data"%in%h5ls.out$name) {
    
    object@data_options[["loaded"]] <- TRUE
    if (verbose) message("Loading data...")
    
    for (m in view_names) {
      data[[m]] <- list()
      intercepts[[m]] <- list()
      for (g in group_names) {
        if (on_disk) {
          # as DelayedArrays
          data[[m]][[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("data/%s/%s", m, g) ) )
        } else {
          # as matrices
          data[[m]][[g]] <- h5read(file, sprintf("data/%s/%s", m, g) )
          tryCatch(intercepts[[m]][[g]] <- as.numeric( h5read(file, sprintf("intercepts/%s/%s", m, g) ) ), error = function(e) { NULL })
        }
        # Replace NaN by NA
        data[[m]][[g]][is.nan(data[[m]][[g]])] <- NA # this realised into memory, TO FIX
      }
    }
    
  # Create empty training data (as nested list of empty matrices, with the correct dimensions)
  } else {
    
    object@data_options[["loaded"]] <- FALSE
    
    for (m in view_names) {
      data[[m]] <- list()
      for (g in group_names) {
        data[[m]][[g]] <- .create_matrix_placeholder(rownames = feature_names[[m]], colnames = sample_names[[g]])
      }
    }
  }

  object@data <- data
  object@intercepts <- intercepts


  # Load metadata if any
  if ("samples_metadata" %in% h5ls.out$name) {
    object@samples_metadata <- bind_rows(lapply(group_names, function(g) as.data.frame(h5read(file, sprintf("samples_metadata/%s", g)))))
  }
  if ("features_metadata" %in% h5ls.out$name) {
    object@features_metadata <- bind_rows(lapply(view_names, function(m) as.data.frame(h5read(file, sprintf("features_metadata/%s", m)))))
  }
  
  # ############################
  # ## Load sample covariates ##
  # ############################
  # 
  # if (any(grepl("cov_samples", h5ls.out$group))){
  #   covariates <- list()
  #   for (g in group_names) {
  #     if (on_disk) {
  #       # as DelayedArrays
  #       covariates[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("cov_samples/%s", g) ) )
  #     } else {
  #       # as matrices
  #       covariates[[g]] <- h5read(file, sprintf("cov_samples/%s", g) )
  #     }    
  #   }
  # } else covariates <- NULL
  # object@covariates <- covariates

  # if (any(grepl("cov_samples_transformed", h5ls.out$group))){
  #   covariates_warped <- list()
  #   for (g in group_names) {
  #     if (on_disk) {
  #       # as DelayedArrays
  #       covariates_warped[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("cov_samples_transformed/%s", g) ) )
  #     } else {
  #       # as matrices
  #       covariates_warped[[g]] <- h5read(file, sprintf("cov_samples_transformed/%s", g) )
  #     }    
  #   }
  # } else covariates_warped <- NULL
  # object@covariates_warped <- covariates_warped
  
  # #######################
  # ## Load interpolated factor values ##
  # #######################
  # 
  # interpolated_Z <- list()
  # if (isTRUE(load_interpol_Z)) {
  #   
  #   if (isTRUE(verbose)) message("Loading interpolated factor values...")
  #   
  #   for (g in group_names) {
  #     interpolated_Z[[g]] <- list()
  #     if (on_disk) {
  #       # as DelayedArrays
  #       # interpolated_Z[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("Z_predictions/%s", g) ) )
  #     } else {
  #       # as matrices
  #       tryCatch( {
  #         interpolated_Z[[g]][["mean"]] <- h5read(file, sprintf("Z_predictions/%s/mean", g) )
  #       }, error = function(x) { print("Predicitions of Z not found, not loading it...") })
  #       tryCatch( {
  #         interpolated_Z[[g]][["variance"]] <- h5read(file, sprintf("Z_predictions/%s/variance", g) )
  #       }, error = function(x) { print("Variance of predictions of Z not found, not loading it...") })
  #       tryCatch( {
  #         interpolated_Z[[g]][["new_values"]] <- h5read(file, "Z_predictions/new_values")
  #       }, error = function(x) { print("New values of Z not found, not loading it...") })
  #     }
  #   }
  # }
  # object@interpolated_Z <- interpolated_Z
  
  #######################
  ## Load expectations ##
  #######################

  expectations <- list()
  node_names <- h5ls.out[h5ls.out$group=="/expectations","name"]

  if (verbose) message(paste0("Loading expectations for ", length(node_names), " nodes..."))

  if ("AlphaW" %in% node_names)
    expectations[["AlphaW"]] <- h5read(file, "expectations/AlphaW")[view_names]
  if ("AlphaZ" %in% node_names)
    expectations[["AlphaZ"]] <- h5read(file, "expectations/AlphaZ")[group_names]
  if ("Sigma" %in% node_names)
    expectations[["Sigma"]] <- h5read(file, "expectations/Sigma")
  if ("Z" %in% node_names)
    expectations[["Z"]] <- h5read(file, "expectations/Z")[group_names]
  if ("W" %in% node_names)
    expectations[["W"]] <- h5read(file, "expectations/W")[view_names]
  if ("ThetaW" %in% node_names)
    expectations[["ThetaW"]] <- h5read(file, "expectations/ThetaW")[view_names]
  if ("ThetaZ" %in% node_names)
    expectations[["ThetaZ"]] <- h5read(file, "expectations/ThetaZ")[group_names]
  # if ("Tau" %in% node_names)
  #   expectations[["Tau"]] <- h5read(file, "expectations/Tau")
  
  object@expectations <- expectations

  
  ########################
  ## Load model options ##
  ########################

  if (verbose) message("Loading model options...")

  tryCatch( {
    object@model_options <- as.list(h5read(file, 'model_options', read.attributes = TRUE))
  }, error = function(x) { print("Model options not found, not loading it...") })

  # Convert True/False strings to logical values
  for (i in names(object@model_options)) {
    if (object@model_options[i] == "False" || object@model_options[i] == "True") {
      object@model_options[i] <- as.logical(object@model_options[i])
    } else {
      object@model_options[i] <- object@model_options[i]
    }
  }

  ##########################################
  ## Load training options and statistics ##
  ##########################################

  if (verbose) message("Loading training options and statistics...")

  # Load training options
  if (length(object@training_options) == 0) {
    tryCatch( {
      object@training_options <- as.list(h5read(file, 'training_opts', read.attributes = TRUE))
    }, error = function(x) { print("Training opts not found, not loading it...") })
  }

  # Load training statistics
  tryCatch( {
    object@training_stats <- h5read(file, 'training_stats', read.attributes = TRUE)
    object@training_stats <- h5read(file, 'training_stats', read.attributes = TRUE)
  }, error = function(x) { print("Training stats not found, not loading it...") })

  #############################
  ## Load covariates options ##
  #############################
  # 
  # if (any(grepl("cov_samples", h5ls.out$group))) { 
  #   if (isTRUE(verbose)) message("Loading covariates options...")
  #   tryCatch( {
  #     object@mefisto_options <- as.list(h5read(file, 'smooth_opts', read.attributes = TRUE))
  #   }, error = function(x) { print("Covariates options not found, not loading it...") })
  #   
  #   # Convert True/False strings to logical values
  #   for (i in names(object@mefisto_options)) {
  #     if (object@mefisto_options[i] == "False" | object@mefisto_options[i] == "True") {
  #       object@mefisto_options[i] <- as.logical(object@mefisto_options[i])
  #     } else {
  #       object@mefisto_options[i] <- object@mefisto_options[i]
  #     }
  #   }
  #   
  # }
  # 
  
    
  #######################################
  ## Load variance explained estimates ##
  #######################################
  
  if ("variance_explained" %in% h5ls.out$name) {
    r2_list <- list(
      r2_total = h5read(file, "variance_explained/r2_total")[group_names],
      r2_per_factor = h5read(file, "variance_explained/r2_per_factor")[group_names]
    )
    object@cache[["variance_explained"]] <- r2_list
  }
  
  # Hack to fix the problems where variance explained values range from 0 to 1 (%)
  if (max(sapply(object@cache$variance_explained$r2_total,max,na.rm=TRUE),na.rm=TRUE)<1) {
    for (m in 1:length(view_names)) {
      for (g in 1:length(group_names)) {
        object@cache$variance_explained$r2_total[[g]][[m]] <- 100 * object@cache$variance_explained$r2_total[[g]][[m]]
        object@cache$variance_explained$r2_per_factor[[g]][,m] <- 100 * object@cache$variance_explained$r2_per_factor[[g]][,m]
      }
    }
  }
  
  ##############################
  ## Specify dimensionalities ##
  ##############################
  
  # Specify dimensionality of the data
  object@dimensions[["M"]] <- length(data)                            # number of views
  object@dimensions[["G"]] <- length(data[[1]])                       # number of groups
  object@dimensions[["N"]] <- sapply(data[[1]], ncol)                 # number of samples (per group)
  object@dimensions[["D"]] <- sapply(data, function(e) nrow(e[[1]]))  # number of features (per view)
  # object@dimensions[["C"]] <- nrow(covariates[[1]])                        # number of covariates
  object@dimensions[["K"]] <- ncol(object@expectations$Z[[1]])        # number of factors
  
  # Assign sample and feature names (slow for large matrices)
  if (verbose) message("Assigning names to the different dimensions...")

  # Create default features names if they are null
  if (is.null(feature_names)) {
    print("Features names not found, generating default: feature1_view1, ..., featureD_viewM")
    feature_names <- lapply(seq_len(object@dimensions[["M"]]),
                            function(m) sprintf("feature%d_view_&d", as.character(seq_len(object@dimensions[["D"]][m])), m))
  } else {
    # Check duplicated features names
    all_names <- unname(unlist(feature_names))
    duplicated_names <- unique(all_names[duplicated(all_names)])
    if (length(duplicated_names)>0) 
      warning("There are duplicated features names across different views. We will add the suffix *_view* only for those features 
            Example: if you have both TP53 in mRNA and mutation data it will be renamed to TP53_mRNA, TP53_mutation")
    for (m in names(feature_names)) {
      tmp <- which(feature_names[[m]] %in% duplicated_names)
      if (length(tmp)>0) feature_names[[m]][tmp] <- paste(feature_names[[m]][tmp], m, sep="_")
    }
  }
  features_names(object) <- feature_names
  
  # Create default samples names if they are null
  if (is.null(sample_names)) {
    print("Samples names not found, generating default: sample1, ..., sampleN")
    sample_names <- lapply(object@dimensions[["N"]], function(n) paste0("sample", as.character(seq_len(n))))
  }
  samples_names(object) <- sample_names

  # Add covariates names
  # if(!is.null(object@covariates)){
  #   # Create default covariates names if they are null
  #   if (is.null(covariate_names)) {
  #     print("Covariate names not found, generating default: covariate1, ..., covariateC")
  #     covariate_names <- paste0("sample", as.character(seq_len(object@dimensions[["C"]])))
  #   }
  #   covariates_names(object) <- covariate_names
  # }
  
  # Set views names
  if (is.null(names(object@data))) {
    print("Views names not found, generating default: view1, ..., viewM")
    view_names <- paste0("view", as.character(seq_len(object@dimensions[["M"]])))
  }
  views_names(object) <- view_names
  
  # Set groups names
  if (is.null(names(object@data[[1]]))) {
    print("Groups names not found, generating default: group1, ..., groupG")
    group_names <- paste0("group", as.character(seq_len(object@dimensions[["G"]])))
  }
  groups_names(object) <- group_names
  
  # Set factors names
  factors_names(object)  <- paste0("Factor", as.character(seq_len(object@dimensions[["K"]])))
  
  ###################
  ## Parse factors ##
  ###################
  
  # Calculate variance explained estimates per factor
  if (is.null(object@cache[["variance_explained"]])) {
    object@cache[["variance_explained"]] <- calculate_variance_explained(object)
  } 
  
  # Remove inactive factors
  if (remove_inactive_factors) {
    r2 <- rowSums(do.call('cbind', lapply(object@cache[["variance_explained"]]$r2_per_factor, rowSums, na.rm=TRUE)))
    var.threshold <- 0.0001
    if (all(r2 < var.threshold)) {
      warning(sprintf("All %s factors were found to explain little or no variance so remove_inactive_factors option has been disabled.", length(r2)))
    } else if (any(r2 < var.threshold)) {
      object <- subset_factors(object, which(r2>=var.threshold))
      message(sprintf("%s factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)", sum(r2 < var.threshold)))
    }
  }
  
  # [Done in mofapy2] Sort factors by total variance explained
  if (sort_factors && object@dimensions$K>1) {

    # Sanity checks
    if (verbose) message("Re-ordering factors by their variance explained...")

    # Calculate variance explained per factor across all views
    r2 <- rowSums(sapply(object@cache[["variance_explained"]]$r2_per_factor, function(e) rowSums(e, na.rm = TRUE)))
    order_factors <- c(names(r2)[order(r2, decreasing = TRUE)])

    # re-order factors
    object <- subset_factors(object, order_factors)
  }

  # Mask outliers
  if (remove_outliers) {
    if (verbose) message("Removing outliers...")
    object <- .detect_outliers(object)
  }
  
  # Mask intercepts for non-Gaussian data
  if (any(object@model_options$likelihoods!="gaussian")) {
    for (m in names(which(object@model_options$likelihoods!="gaussian"))) {
      for (g in names(object@intercepts[[m]])) {
        object@intercepts[[m]][[g]] <- NA
      }
    }
  }

  # ######################
  # ## Quality controls ##
  # ######################
  # 
  # if (verbose) message("Doing quality control...")
  # object <- .quality_control(object, verbose = verbose)
  # 
  return(object)
}

mofa_trained <- load_model(outfile)
Recalculating total variance explained values (r2_total)...
10 factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)
samples_names(mofa_trained) <- samples_names(object)
samples_metadata(mofa_trained)
rownames(samples_metadata(mofa_trained)) <- samples_metadata(mofa_trained)[["sample"]]

Visualize variance explained by factors

plot_variance_explained(mofa_trained, x='factor', y='group', split_by = 'view', plot_total = TRUE, max_r2 = 50)[[1]] +
  theme(axis.text.x = element_text(angle=45, hjust=1))


get_variance_explained(mofa_trained, as.data.frame = TRUE)[[2]] %>%
  ggplot(aes(group, value)) +
  geom_col() +
  coord_flip() +
  ylab("Var. (%)") +
  theme_classic(base_size=14)

Plot by celltype

get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>%
  ggplot(aes(factor, value)) + geom_col() +
  coord_flip() +
  facet_wrap(group~., ncol = 6, scales = "free_x")

plot_factor_cor(mofa_trained, method = "spearman")

## Correlation with principal components
pcs <- reducedDim(sce)
fctrs <- get_factors(mofa_trained) %>%
  purrr::reduce(rbind)

corrplot::corrplot(cor(pcs, fctrs[rownames(pcs),]))

Factor ID plots

for (f in 1:mofa_trained@dimensions$K){
  print(paste0("Saving ID for Factor ", f, "..."))
  save_factor_id(mofa_trained, f=f, figdir = figdir)  
}
[1] "Saving ID for Factor 1..."
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.
[1] "Saving ID for Factor 2..."
[1] "Saving ID for Factor 3..."
[1] "Saving ID for Factor 4..."
[1] "Saving ID for Factor 5..."
[1] "Saving ID for Factor 6..."
[1] "Saving ID for Factor 7..."
[1] "Saving ID for Factor 8..."
[1] "Saving ID for Factor 9..."
[1] "Saving ID for Factor 10..."
[1] "Saving ID for Factor 11..."
[1] "Saving ID for Factor 12..."
[1] "Saving ID for Factor 13..."
[1] "Saving ID for Factor 14..."
[1] "Saving ID for Factor 15..."
[1] "Saving ID for Factor 16..."
[1] "Saving ID for Factor 17..."
[1] "Saving ID for Factor 18..."
[1] "Saving ID for Factor 19..."
[1] "Saving ID for Factor 20..."

KNN graph per celltype

plot_ct_KNN_graph(knn, color_by = 'Factor5') 
Using `stress` as default layout

connectivity_test_ls <- lapply(all_groups, function(g) test_conn_group(mofa_trained, g))
```r
connectivity_test_df %>%
  group_by(group) %>%
  mutate(mean_val=median(score)) %>%
  ungroup() %>%
  arrange(-mean_val) %>%
  mutate(group=factor(group, levels=unique(group))) %>%
  ggplot(aes(organ, log1p(score))) +
  geom_col(fill=\grey\) +
  geom_col(data=. %>% filter(is_signif), aes(fill=organ)) +
  scale_fill_manual(values=org_colors)  +
  coord_flip() +
  facet_grid(group~.) +
  theme(strip.text.y = element_text(angle=0))

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->


#### Expression of top R2 factors


<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuZm9yIChnIGluIGFsbF9ncm91cHMpe1xuICBmcyA8LSBnZXRfdG9wX2ZhY3Rvcl9wZXJfY2VsbHR5cGUobW9mYV90cmFpbmVkLCBnLCBtaW5fUjI9MylcbiAgdG9wX3Bsb3RzIDwtIGxhcHBseShmcywgZnVuY3Rpb24oeCkgKHBsb3RfZGF0YV90b3Bfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGcsIHgsIHdoaWNoPVwidG9wXCIpICsgcmVtb3ZlX3hfYXhpcygpKSAvICBcbiAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfZGF0YV90b3Bfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGcsIHgsIHdoaWNoPVwiYm90dG9tXCIpICsgZ2d0aXRsZShcIlwiKVxuICApXG4gIGZ1bGxfcGwgPC13cmFwX3Bsb3RzKHRvcF9wbG90cywgbmNvbD0xKSBcbiAgZ2dzYXZlKGdsdWUoXCJ7ZmlnZGlyfS90b3BfZmFjdG9yc19leHByX3tnfS5wZGZcIikscGxvdD1mdWxsX3BsLCAgd2lkdGg9MTIsIGhlaWdodCA9IDcqbGVuZ3RoKHRvcF9wbG90cykpXG59XG5cbmBgYCJ9 -->

```r
for (g in all_groups){
  fs <- get_top_factor_per_celltype(mofa_trained, g, min_R2=3)
  top_plots <- lapply(fs, function(x) (plot_data_top_weights(mofa_trained, g, x, which="top") + remove_x_axis()) /  
                        plot_data_top_weights(mofa_trained, g, x, which="bottom") + ggtitle("")
  )
  full_pl <-wrap_plots(top_plots, ncol=1) 
  ggsave(glue("{figdir}/top_factors_expr_{g}.pdf"),plot=full_pl,  width=12, height = 7*length(top_plots))
}
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"
Joining, by = "sample"

GSEA

```r
# BiocManager::install(\MOFAdata\)
library(MOFAdata)
utils::data(reactomeGS)
head(rownames(reactomeGS))

## Remove row with NA
reactomeGS <- reactomeGS[!is.na(rownames(reactomeGS)),]

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->



<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxubGlicmFyeShFbnNEYi5Ic2FwaWVucy52ODYpXG5oZy5wYWlycyA8LSByZWFkUkRTKHN5c3RlbS5maWxlKFxcZXhkYXRhXFwsIFxcaHVtYW5fY3ljbGVfbWFya2Vycy5yZHNcXCwgcGFja2FnZT1cXHNjcmFuXFwpKVxuYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KVxuZGV0YWNoKHBhY2thZ2U6RW5zRGIuSHNhcGllbnMudjg2KVxuZGV0YWNoKHBhY2thZ2U6ZW5zZW1ibGRiKVxuXG4jIGdlbmVfbmFtZV8yX2lkIDwtIGZ1bmN0aW9uKGdlbmUpe1xuIyAgICByZXR1cm4oYWxsX2dlbmVzW2FsbF9nZW5lcyRnZW5lX25hbWU9PWdlbmUsXSRnZW5lX2lkWzFdKVxuIyB9XG4jIFxuIyBnZW5lX2lkcyA8LSBzYXBwbHkobW9mYV90cmFpbmVkQGZlYXR1cmVzX21ldGFkYXRhJGZlYXR1cmUsIGdlbmVfbmFtZV8yX2lkKVxuIyByb3dEYXRhKHNjZSlbXFxnZW5lX2lkXFxdIDwtIGdlbmVfaWRzXG4jIHJvd0RhdGEoc2NlKVtcXGdlbmVfbmFtZVxcXSA8LSByb3duYW1lcyhzY2UpXG5cbmdlbmVfbmFtZXNfcmVhY3RvbWUgPC0gYWxsX2dlbmVzW2NvbG5hbWVzKHJlYWN0b21lR1MpXSRnZW5lX25hbWVcbmNvbG5hbWVzKHJlYWN0b21lR1MpIDwtIGdlbmVfbmFtZXNfcmVhY3RvbWVcbmBgYFxuYGBgIn0= -->

```r
```r
library(EnsDb.Hsapiens.v86)
hg.pairs <- readRDS(system.file(\exdata\, \human_cycle_markers.rds\, package=\scran\))
all_genes <- ensembldb::genes(EnsDb.Hsapiens.v86)
detach(package:EnsDb.Hsapiens.v86)
detach(package:ensembldb)

# gene_name_2_id <- function(gene){
#    return(all_genes[all_genes$gene_name==gene,]$gene_id[1])
# }
# 
# gene_ids <- sapply(mofa_trained@features_metadata$feature, gene_name_2_id)
# rowData(sce)[\gene_id\] <- gene_ids
# rowData(sce)[\gene_name\] <- rownames(sce)

gene_names_reactome <- all_genes[colnames(reactomeGS)]$gene_name
colnames(reactomeGS) <- gene_names_reactome

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->


Subset to genes tested

<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxucmVhY3RvbWVHU191bml2ZXJzZSA8LSByZWFjdG9tZUdTWywgY29sbmFtZXMocmVhY3RvbWVHUykgJWluJSBtb2ZhX3RyYWluZWRAZmVhdHVyZXNfbWV0YWRhdGEkZmVhdHVyZV1cbmBgYFxuYGBgIn0= -->

```r
```r
reactomeGS_universe <- reactomeGS[, colnames(reactomeGS) %in% mofa_trained@features_metadata$feature]

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->




<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuIyBHU0VBIG9uIHBvc2l0aXZlIHdlaWdodHMsIHdpdGggZGVmYXVsdCBvcHRpb25zXG5yZXMucG9zaXRpdmUgPC0gcnVuX2VucmljaG1lbnQobW9mYV90cmFpbmVkLFxuICB2aWV3PSdzY2FsZWRfbG9nY291bnRzJyxcbiAgIyBzdGF0aXN0aWNhbC50ZXN0ID0gJ2Nvci5hZGoucGFyYW1ldHJpYycsXG4gIGZlYXR1cmUuc2V0cyA9IHJlYWN0b21lR1NfdW5pdmVyc2UsIFxuICBzaWduID0gXFxwb3NpdGl2ZVxcLFxuKVxuXG4jIEdTRUEgb24gbmVnYXRpdmUgd2VpZ2h0cywgd2l0aCBkZWZhdWx0IG9wdGlvbnNcbnJlcy5uZWdhdGl2ZSA8LSBydW5fZW5yaWNobWVudChtb2ZhX3RyYWluZWQsIFxuICB2aWV3PSdzY2FsZWRfbG9nY291bnRzJyxcbiAgIyBzdGF0aXN0aWNhbC50ZXN0ID0gJ2Nvci5hZGoucGFyYW1ldHJpYycsXG4gIGZlYXR1cmUuc2V0cyA9IHJlYWN0b21lR1NfdW5pdmVyc2UsIFxuICBzaWduID0gXFxuZWdhdGl2ZVxcXG4pXG5cblxuZm9yIChmIGluIDE6bW9mYV90cmFpbmVkQGRpbWVuc2lvbnMkSyl7XG4gIGlmIChtaW4ocmVzLnBvc2l0aXZlJHB2YWwuYWRqWyxwYXN0ZTAoXFxGYWN0b3JcXCwgZildKSA8IDAuMSkge1xuICAgIHByaW50KHBsb3RfZW5yaWNobWVudChyZXMucG9zaXRpdmUsIGZhY3RvciA9IGYsIGFscGhhPTAuMSkgKyBnZ3RpdGxlKFxcUG9zaXRpdmUgd2VpZ2h0c1xcKSArXG4gICAgICAgICAgICBwbG90X2VucmljaG1lbnQocmVzLm5lZ2F0aXZlLCBmYWN0b3IgPSBmLCBhbHBoYT0wLjEpICsgZ2d0aXRsZShcXE5lZ2F0aXZlIHdlaWdodHNcXCkgK1xuICAgICAgICAgICAgICBwbG90X2Fubm90YXRpb24odGl0bGU9cGFzdGUwKFxcRmFjdG9yXFwsIGYpKSlcbiAgICAgIH1cbiAgfVxuYGBgXG5gYGAifQ== -->

```r
```r
# GSEA on positive weights, with default options
res.positive <- run_enrichment(mofa_trained,
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = \positive\,
)

# GSEA on negative weights, with default options
res.negative <- run_enrichment(mofa_trained, 
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = \negative\
)


for (f in 1:mofa_trained@dimensions$K){
  if (min(res.positive$pval.adj[,paste0(\Factor\, f)]) < 0.1) {
    print(plot_enrichment(res.positive, factor = f, alpha=0.1) + ggtitle(\Positive weights\) +
            plot_enrichment(res.negative, factor = f, alpha=0.1) + ggtitle(\Negative weights\) +
              plot_annotation(title=paste0(\Factor\, f)))
      }
  }

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->



<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuc2lnbmlmX3BhdGh3YXlzIDwtIHJvd25hbWVzKGRhdGEuZnJhbWUocmVzLm5lZ2F0aXZlJHB2YWwuYWRqKSlbb3JkZXIoZGF0YS5mcmFtZShyZXMubmVnYXRpdmUkcHZhbC5hZGopW1tcXEZhY3RvcjhcXF1dKVswOjEwXV1cbmNvbG5hbWVzKHJlYWN0b21lR1NfdW5pdmVyc2UpW3JlYWN0b21lR1NfdW5pdmVyc2Vbc2lnbmlmX3BhdGh3YXlzWzVdLF09PTFdXG5wbG90X2VucmljaG1lbnRfZGV0YWlsZWQocmVzLm5lZ2F0aXZlLCBmYWN0b3IgPSA4KVxuYGBgXG5gYGAifQ== -->

```r
```r
signif_pathways <- rownames(data.frame(res.negative$pval.adj))[order(data.frame(res.negative$pval.adj)[[\Factor8\]])[0:10]]
colnames(reactomeGS_universe)[reactomeGS_universe[signif_pathways[5],]==1]
plot_enrichment_detailed(res.negative, factor = 8)

```


Notes

  • Factor2 separates BM from rest
  • Factor5: immature VS mature B cell phenotype, separates mature B cells and B1 cells in liver and BM from the others, more mature phenotype (lower expr of VPREB1 and co.)

–> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –>

–> –> –> –> –>

–> –> –>

LS0tCnRpdGxlOiAiRmFjdG9yIEFuYWx5c2lzIGZvciB3aXRoaW4tY2VsbHR5cGUgZGlmZmVyZW5jZXMgb24gb24gcGFuLWZldGFsIGltbXVuZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgRW52IHNldHVwCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKHJlbnYpCiMgcmVudjo6aW5pdCgpCiMgcmVudjo6aW5zdGFsbCgicmV0aWN1bGF0ZSIpCiMgcmVudjo6dXNlX3B5dGhvbigpCiMgCiMgcHlfcGtncyA8LSBjKAojICAgICAic2NhbnB5IiwKIyAgICAgImFubmRhdGEiLAojICAgICAibW9mYXB5MiIKIyApCiMgCiMgcmV0aWN1bGF0ZTo6cHlfaW5zdGFsbChweV9wa2dzKQojIAojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKGMoIlNpbmdsZUNlbGxFeHBlcmltZW50IiwgInNjcmFuIiwgImJhdGNoZWxvciIsICJzY2F0ZXIiKSkKIyBpbnN0YWxsLnBhY2thZ2VzKGMoInBhdGNod29yayIpKQpgYGAKCmBgYHtyfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkoTU9GQTIpCiAgbGlicmFyeShNYXRyaXgpCiAgbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKICBsaWJyYXJ5KHNjcmFuKQogIGxpYnJhcnkoZ2x1ZSkKICBsaWJyYXJ5KHNjYXRlcikKICBsaWJyYXJ5KHBhdGNod29yaykKICBsaWJyYXJ5KGJhdGNoZWxvcikKICBsaWJyYXJ5KHJoZGY1KQogICMgbGlicmFyeShnZ3JhcGgpCiAgfQogICkKYGBgCgpEZWZpbmUgcGxvdHRpbmcgdXRpbHMKYGBge3J9CnJlbW92ZV94X2F4aXMgPC0gZnVuY3Rpb24oKXsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpICAKfQoKcmVtb3ZlX3lfYXhpcyA8LSBmdW5jdGlvbigpewogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSkgIAp9CgpvcmdfY29sb3JzIDwtIHJlYWRfY3N2KCJ+L1Bhbl9mZXRhbF9pbW11bmUvbWV0YWRhdGEvb3JnYW5fY29sb3JzLmNzdiIpCm9yZ19jb2xvcnMgPC0gc2V0TmFtZXMob3JnX2NvbG9ycyRjb2xvciwgb3JnX2NvbG9ycyRvcmdhbikKYGBgCgpgYGB7cn0KZmlnZGlyIDwtICJ+L21vdW50L2dkcml2ZS9QYW5fZmV0YWwvVXBkYXRlc19hbmRfcHJlc2VudGF0aW9ucy9maWd1cmVzL01PRkFfYW5hbHlzaXMvIgppZiAoIWRpci5leGlzdHMoZmlnZGlyKSl7IGRpci5jcmVhdGUoZmlnZGlyKSB9CmBgYAoKIyMgTG9hZCBwc2V1ZG9idWxrZWQgZGF0YQoKYGBge3J9CnNwbGl0ID0gIkxZTVBIT0lEIgppbmRpciA8LSBnbHVlKCIvbmZzL3RlYW0yMDUvZWQ2L2RhdGEvRmV0YWxfaW1tdW5lL0xNTV9kYXRhL0xNTV9pbnB1dF97c3BsaXR9X1BCVUxLLyIpCgptYXRyaXggPC0gcmVhZE1NKGZpbGUgPSBwYXN0ZTAoaW5kaXIsICJtYXRyaXgubXR4Lmd6IikpCmNvbGRhdGEgPC0gcmVhZC5jc3YoZmlsZSA9IHBhc3RlMChpbmRpciwgIm1ldGFkYXRhLmNzdi5neiIpKSAgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJYIikKcm93ZGF0YSA8LSByZWFkLmNzdihmaWxlID0gcGFzdGUwKGluZGlyLCAiZ2VuZS5jc3YuZ3oiKSkgCgojIyBNYWtlIFNpbmdsZUNlbGxFeHBlcmltZW50IG9iagpzY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQobGlzdChsb2djb3VudHMgPSB0KG1hdHJpeCkpLCBjb2xEYXRhID0gY29sZGF0YSkKcm93bmFtZXMoc2NlKSA8LSBtYWtlLnVuaXF1ZShyb3dkYXRhJEdlbmVOYW1lKSAKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQojIyBQbG90IG51bWJlciBvZiBjZWxscyBwZXIgb3JnYW4vY2VsbHR5cGUgcGFpcgpuX2NlbGxzX2hlYXRtYXAgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscz1zdW0obl9jZWxscykpICU+JQogIGdncGxvdChhZXMoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsPWxvZzEwKG5fY2VsbHMpKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bl9jZWxscyksIGNvbG9yPSJ3aGl0ZSIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxhYigiY2VsbHR5cGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQoKbl9zYW1wbGVzX2hlYXRtYXAgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bl9zYW1wbGVzKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bl9zYW1wbGVzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbj0iY2l2aWRpcyIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxhYigiY2VsbHR5cGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkpCgpuX2NlbGxzX2hlYXRtYXAgLyBuX3NhbXBsZXNfaGVhdG1hcApgYGAKCiMjIFByZXByb2Nlc3NpbmcKCiMjIyBGaWx0ZXJpbmcgc2FtcGxlcwoKYGBge3J9CiMjIEZpbHRlciBvdXQgc2FtcGxlcyB3aXRoIGxlc3MgdGhhbiAyMCBjZWxscwpzY2UgPC0gc2NlWyxzY2Ukbl9jZWxscyA+IDIwXQoKIyBFeGNsdWRlIGNlbGx0eXBlcyBwcmVzZW50IGluIGp1c3Qgb25lIG9yZ2FuCmtlZXBfY3QgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGRwbHlyOjpzZWxlY3Qob3JnYW4sIGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lCiAgc3VtbWFyaXNlKG49bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZmlsdGVyKG4gPiAxKSAlPiUKICBwdWxsKGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCgpzY2UgPC0gc2NlWyxzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbiAlaW4lIGtlZXBfY3RdCgojIEZpbHRlciBvdXQgY2VsbHR5cGVzIHdpdGggbGVzcyB0aGFuIDEwIHNhbXBsZXMKa2VlcF9jdCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lCiAgc3VtbWFyaXNlKG5fc2FtcGxlcz1uKCkpICU+JQogIGZpbHRlcihuX3NhbXBsZXMgPj0gMTApICU+JQogIHB1bGwoYW5ub19sdmxfMl9maW5hbF9jbGVhbikKCnNjZSA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuICVpbiUga2VlcF9jdF0KCiMjIEV4Y2x1ZGUgbG93IHF1YWxpdHkgY2x1c3RlcnMKYW5ub19ncm91cHMgPC0ganNvbmxpdGU6OmZyb21KU09OKHR4dCA9ICAifi9QYW5fZmV0YWxfaW1tdW5lL21ldGFkYXRhL2Fubm9fZ3JvdXBzLmpzb24iKQpzY2UgPC0gc2NlWywhc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4gJWluJSBhbm5vX2dyb3VwcyRPVEhFUl0KCiMjIEV4Y2x1ZGUgZG9ub3IgRjE5IChsb3cgUSkKc2NlIDwtIHNjZVssIXNjZSRkb25vciAlaW4lIGMoJ0YxOScpXQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CiMjIFBsb3QgbnVtYmVyIG9mIGNlbGxzIHBlciBvcmdhbi9jZWxsdHlwZSBwYWlyCm5fY2VsbHNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX2NlbGxzPXN1bShuX2NlbGxzKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bG9nMTAobl9jZWxscykpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX2NlbGxzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpCgpuX3NhbXBsZXNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUKICBnZ3Bsb3QoYWVzKGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbD1uX3NhbXBsZXMpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX3NhbXBsZXMpLCBjb2xvcj0id2hpdGUiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uPSJjaXZpZGlzIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkKCm5fY2VsbHNfaGVhdG1hcCAvIG5fc2FtcGxlc19oZWF0bWFwCmBgYAoKIyMjIFRlY2huaWNhbCBlZmZlY3QgY29ycmVjdGlvbiAKCmBgYHtyfQojIyBGZWF0dXJlIHNlbGVjdGlvbiB3IHNjcmFuIFdJVEhJTiBDRUxMVFlQRQphbm5vX2dyb3VwcyA8LSBzcGxpdChjb2xuYW1lcyhzY2UpLCBzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbikKYWxsX2h2Z3MgPC0gYygpCmZvciAoaSBpbiBhbm5vX2dyb3Vwcyl7CiAgZGVjIDwtIG1vZGVsR2VuZVZhcihzY2VbLGldKQogIGh2Z3MgPC0gZ2V0VG9wSFZHcyhkZWMsIG4gPSAxMDAwKQogIGFsbF9odmdzIDwtIHVuaW9uKGFsbF9odmdzLCBodmdzKQogIH0KCnNjZSA8LSBzY2Vbd2hpY2gocm93U3Vtcyhsb2djb3VudHMoc2NlKSkgPiAwKSxdCnNjZQpgYGAKCkVEQSB3aXRoIFBDQQpgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1fQpzY2UgPC0gcnVuUENBKHNjZSwgc2NhbGU9VFJVRSwgbmNvbXBvbmVudHM9MzAsIAogICAgICAgICAgICAgIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBzdWJzZXRfcm93PWFsbF9odmdzKQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJkb25vciIsIG5jb21wb25lbnRzPTYpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9Im1ldGhvZCIsIG5jb21wb25lbnRzPTYpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9Im9yZ2FuIiwgbmNvbXBvbmVudHM9MTApCmBgYAoKTWluaW1pemUgb2J2aW91cyB0ZWNobmljYWwgZWZmZWN0cyAoM0dFWC81R0VYLCBkb25vcikgdXNpbmcgbGluZWFyIHJlZ3Jlc3Npb24gKGZvbGxvd2luZyBwcm9jZWR1cmUgZnJvbSBbT1NDQV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS9pbnRlZ3JhdGluZy1kYXRhc2V0cy5odG1sI2xpbmVhci1yZWdyZXNzaW9uKSkKCmBgYHtyfQojIyBSZWdyZXNzIHRlY2huaWNhbCBlZmZlY3RzCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmRvbm9yK21ldGhvZCxkYXRhPWNvbERhdGEoc2NlKSkKcmVzaWR1YWxzIDwtIHJlZ3Jlc3NCYXRjaGVzKHNjZSwgYXNzYXkudHlwZSA9ICJsb2djb3VudHMiLCBkZXNpZ24gPSBkZXNpZ24pCmFzc2F5KHNjZSwgImNvcnJlY3RlZF9sb2djb3VudHMiKSA8LSBhcy5tYXRyaXgoYXNzYXkocmVzaWR1YWxzWyxjb2xuYW1lcyhzY2UpXSwgImNvcnJlY3RlZCIpKQoKIyMgUmVncmVzcyBvcmdhbiAoc291cCBlZmZlY3QpCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofm9yZ2FuLGRhdGE9Y29sRGF0YShzY2UpKSAjIyBJbmNsdWRlIG9yZ2FuIHRlcm0gdG8gY2FwdHVyZSBzb3VwCnJlc2lkdWFscyA8LSByZWdyZXNzQmF0Y2hlcyhzY2UsIGFzc2F5LnR5cGUgPSAiY29ycmVjdGVkX2xvZ2NvdW50cyIsIGRlc2lnbiA9IGRlc2lnbikKYXNzYXkoc2NlLCAiY29ycmVjdGVkX2xvZ2NvdW50cyIpIDwtIGFzLm1hdHJpeChhc3NheShyZXNpZHVhbHNbLGNvbG5hbWVzKHNjZSldLCAiY29ycmVjdGVkIikpCgpgYGAKCkNoZWNrIHJlZ3Jlc3Npb24gaGFzIGFuIGVmZmVjdCByZXBlYXRpbmcgUENBCmBgYHtyLCBmaWcuaGVpZ2h0PTE1LCBmaWcud2lkdGg9MTV9CnNjZSA8LSBydW5QQ0Eoc2NlLCBzY2FsZT1UUlVFLCBuY29tcG9uZW50cz0zMCwgZXhwcnNfdmFsdWVzID0gImNvcnJlY3RlZF9sb2djb3VudHMiKQoKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0ibWV0aG9kIiwgbmNvbXBvbmVudHM9NikKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0iZG9ub3IiLCBuY29tcG9uZW50cz02KQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJvcmdhbiIsIG5jb21wb25lbnRzPTgpCmBgYAoKIyMjIEZlYXR1cmUgc2VsZWN0aW9uCgpgYGB7cn0KIyMgRmVhdHVyZSBzZWxlY3Rpb24gdyBzY3JhbiBXSVRISU4gQ0VMTFRZUEUKYW5ub19ncm91cHMgPC0gc3BsaXQoY29sbmFtZXMoc2NlKSwgc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCmFsbF9odmdzIDwtIGMoKQpmb3IgKGkgaW4gYW5ub19ncm91cHMpewogIGRlYyA8LSBtb2RlbEdlbmVWYXIoc2NlWyxpXSwgYXNzYXkudHlwZSA9ICJjb3JyZWN0ZWRfbG9nY291bnRzIikKICBodmdzIDwtIGdldFRvcEhWR3MoZGVjLCBuID0gMTAwMCkKICBhbGxfaHZncyA8LSB1bmlvbihhbGxfaHZncywgaHZncykKICB9CmBgYAoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSwgbl9jZWxscz1zdW0obl9jZWxscykpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMobl9zYW1wbGVzLCBsb2cxMChuX2NlbGxzKSkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KHNpemU9MC44LCBhbHBoYT0wLjYpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0gLS0+CjwhLS0gcCA8LSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlKVssMToyXSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG9yZ2FuPXNjZSRvcmdhbiwgY2VsbHR5cGU9c2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShjb2xvcj1pZmVsc2UoY2VsbHR5cGU9PSJNQVRVUkUgQiBDRUxMIiwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzEsIFBDMikpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpIC0tPgoKPCEtLSBwIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGxpYnJhcnkoUkNvbG9yQnJld2VyKSAtLT4KPCEtLSBvcmdfY29sb3JzIDwtIHNldE5hbWVzKGJyZXdlci5wYWwoOSwgIlNldDEiKSwgdW5pcXVlKHNjZSRvcmdhbikpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzY2VfbWF0dXJlQiA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuPT0iTUFUVVJFIEIgQ0VMTCJdIC0tPgo8IS0tIGFzc2F5KHNjZV9tYXR1cmVCLCAic2NhbGVkX2xvZ2NvdW50cyIpIDwtIHQoc2NhbGUodChsb2djb3VudHMoc2NlX21hdHVyZUIpKSkpIC0tPgoKCjwhLS0gc2NlX21hdHVyZUIgPC0gcnVuUENBKHNjZV9tYXR1cmVCLCBzY2FsZT1GQUxTRSwgbmNvbXBvbmVudHM9MzAsIGV4cHJzX3ZhbHVlcyA9ICJzY2FsZWRfbG9nY291bnRzIikgLS0+Cgo8IS0tIGRhdGEuZnJhbWUocmVkdWNlZERpbShzY2VfbWF0dXJlQilbLDI6M10pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShvcmdhbj1zY2VfbWF0dXJlQiRvcmdhbiwgY2VsbHR5cGU9c2NlX21hdHVyZUIkYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNvbG9yPWlmZWxzZShvcmdhbiAlaW4lIGMoIlRIIiwiQk0iKSwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzIsIFBDMykpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemU9MTYpICsgLS0+CjwhLS0gICBnZ3RpdGxlKCJNQVRVUkUgQiBDRUxMIikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzY2VfbWF0dXJlQiA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuPT0iTksiXSAtLT4KPCEtLSBhc3NheShzY2VfbWF0dXJlQiwgInNjYWxlZF9sb2djb3VudHMiKSA8LSB0KHNjYWxlKHQobG9nY291bnRzKHNjZV9tYXR1cmVCKSkpKSAtLT4KCjwhLS0gc2NlX21hdHVyZUIgPC0gcnVuUENBKHNjZV9tYXR1cmVCLCBzY2FsZT1GQUxTRSwgbmNvbXBvbmVudHM9MzAsIGV4cHJzX3ZhbHVlcyA9ICJzY2FsZWRfbG9nY291bnRzIikgLS0+Cgo8IS0tICMjIFZhcmlhbmNlIGV4cGxhaW5lZCAtLT4KPCEtLSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlX21hdHVyZUIpWywxOjRdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUob3JnYW49c2NlX21hdHVyZUIkb3JnYW4sICAtLT4KPCEtLSAgICAgICAgICBjZWxsdHlwZT1zY2VfbWF0dXJlQiRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY29sb3I9aWZlbHNlKG9yZ2FuICVpbiUgYygiR1UiLCAiU1AiKSwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzEsIFBDMykpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemU9MTYpICsgLS0+CjwhLS0gICBnZ3RpdGxlKCJOSyBDRUxMIikgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGRhdGEuZnJhbWUocmVkdWNlZERpbShzY2VfbWF0dXJlQilbLDI6M10pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShvcmdhbj1zY2VfbWF0dXJlQiRvcmdhbiwgY2VsbHR5cGU9c2NlX21hdHVyZUIkYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNvbG9yPWlmZWxzZShjZWxsdHlwZT09Ik1BVFVSRSBCIENFTEwiLCAiRUxQIiwgTkEpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKFBDMiwgUEMzKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoY29sb3I9ImdyZXkiKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgc2l6ZT0yKSArIC0tPgo8IS0tICAgZ2VvbV9ydWcoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIGFscGhhPTAuNSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTcGVjdHJhbCIpIC0tPgo8IS0tIGBgYCAtLT4KCgojIEZBIE1vZGVsIC0gTm9ybWFsIE1PRkEgLyBvbmx5IGNlbGx0eXBlcyBhcyBncm91cHMKCk1ha2UgTU9GQSBvYmplY3QgKFVzZSBjZWxsdHlwZXMgYXMgZ3JvdXBpbmcgY292YXJpYXRlKQoKYGBge3J9Cm1vZmEgPC0gY3JlYXRlX21vZmFfZnJvbV9TaW5nbGVDZWxsRXhwZXJpbWVudChzY2VbYWxsX2h2Z3MsXSwgYXNzYXkgPSAiY29ycmVjdGVkX2xvZ2NvdW50cyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBzID0gImFubm9fbHZsXzJfZmluYWxfY2xlYW4iLCBleHRyYWN0X21ldGFkYXRhID0gVFJVRSkKCnNhdmVSRFMobW9mYSwgZ2x1ZSgne2luZGlyfUxZTVBIT0lEX21vZmFfb2JqX29yZ2FuQ29ycmVjdGVkLlJEUycpKQptb2ZhX29iaiA8LSByZWFkUkRTKGdsdWUoJ3tpbmRpcn1MWU1QSE9JRF9tb2ZhX29ial9vcmdhbkNvcnJlY3RlZC5SRFMnKSkKYGBgCgpgYGB7cn0Kb2JqZWN0IDwtIG1vZmFfb2JqCmBgYAoKClByZXBhcmUgNCB0cmFpbmluZwoKYGBge3J9CgpkYXRhX29wdHMgPC0gZ2V0X2RlZmF1bHRfZGF0YV9vcHRpb25zKG9iamVjdCkKZGF0YV9vcHRzJHVzZV9mbG9hdDMyIDwtIFRSVUUKZGF0YV9vcHRzJGNlbnRlcl9ncm91cHMgPC0gRkFMU0UKb2JqZWN0QGRhdGFfb3B0aW9ucyA8LSBkYXRhX29wdHMKCm1vZGVsX29wdHMgPC0gZ2V0X2RlZmF1bHRfbW9kZWxfb3B0aW9ucyhvYmplY3QpCm1vZGVsX29wdHMkbnVtX2ZhY3RvcnMgPC0gMzAKIyBtb2RlbF9vcHRzJGFyZF9mYWN0b3JzIDwtIEZBTFNFCgp0cmFpbl9vcHRzIDwtIGdldF9kZWZhdWx0X3RyYWluaW5nX29wdGlvbnMob2JqZWN0KQp0cmFpbl9vcHRzJHNlZWQgPC0gMjAyMAp0cmFpbl9vcHRzJGNvbnZlcmdlbmNlX21vZGUgPC0gIm1lZGl1bSIgIyB1c2UgImZhc3QiIGZvciBmYXN0ZXIgdHJhaW5pbmcKdHJhaW5fb3B0cyRzdG9jaGFzdGljIDwtIEZBTFNFCgojIG1lZmlzdG9fb3B0cyA8LSBnZXRfZGVmYXVsdF9tZWZpc3RvX29wdGlvbnMob2JqZWN0KQojIG1lZmlzdG9fb3B0cyR3YXJwaW5nIDwtIEZBTFNFCiMgbWVmaXN0b19vcHRzJHNwYXJzZUdQIDwtIFRSVUUKCm9iamVjdCA8LSBwcmVwYXJlX21vZmEoCiAgb2JqZWN0ID0gb2JqZWN0LAogIGRhdGFfb3B0aW9ucyA9IGRhdGFfb3B0cywKICBtb2RlbF9vcHRpb25zID0gbW9kZWxfb3B0cywKICB0cmFpbmluZ19vcHRpb25zID0gdHJhaW5fb3B0cwopIAoKb2JqZWN0CmBgYAoKIyMgVHJhaW4KCldyYXBwZWQgaW4gYHJ1bl9tb2ZhLlJgCgoKYGBge3J9Cm91dGZpbGUgPC0gZ2x1ZSgne2luZGlyfXtzcGxpdH1fbW9mYV9tb2RlbF9vbmV2aWV3X29yZ2FuQ29ycmVjdGVkLmhkZjUnKQptb2ZhX3RyYWluZWQgPC0gcnVuX21vZmEob2JqZWN0LCBvdXRmaWxlID0gb3V0ZmlsZSkKYGBgCgpgYGB7cn0KIyMjIFR3ZWFraW5nIHRoZSBNT0ZBMiBsb2FkaW5nIGZ1bmN0aW9uIGJlY2F1c2UgdGhlIHF1YWxpdHkgY29udHJvbCBjb21wbGFpbnMKbG9hZF9tb2RlbCA8LSBmdW5jdGlvbihmaWxlLCBzb3J0X2ZhY3RvcnMgPSBUUlVFLCBvbl9kaXNrID0gRkFMU0UsIGxvYWRfZGF0YSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX291dGxpZXJzID0gRkFMU0UsIHJlbW92ZV9pbmFjdGl2ZV9mYWN0b3JzID0gVFJVRSwgdmVyYm9zZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgIGxvYWRfaW50ZXJwb2xfWiA9IEZBTFNFKSB7CgogICMgQ3JlYXRlIG5ldyBNT0ZBb2RlbCBvYmplY3QKICBvYmplY3QgPC0gbmV3KCJNT0ZBIikKICBvYmplY3RAc3RhdHVzIDwtICJ0cmFpbmVkIgogIAogICMgU2V0IG9uX2Rpc2sgb3B0aW9uCiAgaWYgKG9uX2Rpc2spIHsgCiAgICBvYmplY3RAb25fZGlzayA8LSBUUlVFIAogIH0gZWxzZSB7IAogICAgICBvYmplY3RAb25fZGlzayA8LSBGQUxTRSAKICB9CiAgCiAgIyBHZXQgZ3JvdXBzIGFuZCBkYXRhIHNldCBuYW1lcyBmcm9tIHRoZSBoZGY1IGZpbGUgb2JqZWN0CiAgaDVscy5vdXQgPC0gaDVscyhmaWxlLCBkYXRhc2V0aW5mbyA9IEZBTFNFKQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgdHJhaW5pbmcgZGF0YSAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKICAjIExvYWQgbmFtZXMKICBpZiAoInZpZXdzIiAlaW4lIGg1bHMub3V0JG5hbWUpIHsKICAgIHZpZXdfbmFtZXMgPC0gYXMuY2hhcmFjdGVyKCBoNXJlYWQoZmlsZSwgInZpZXdzIilbWzFdXSApCiAgICBncm91cF9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoIGg1cmVhZChmaWxlLCAiZ3JvdXBzIilbWzFdXSApCiAgICBmZWF0dXJlX25hbWVzIDwtIGg1cmVhZChmaWxlLCAiZmVhdHVyZXMiKVt2aWV3X25hbWVzXQogICAgc2FtcGxlX25hbWVzICA8LSBoNXJlYWQoZmlsZSwgInNhbXBsZXMiKVtncm91cF9uYW1lc10gCiAgfSBlbHNlIHsgICMgZm9yIG9sZCBtb2RlbHMKICAgIGZlYXR1cmVfbmFtZXMgPC0gaDVyZWFkKGZpbGUsICJmZWF0dXJlcyIpCiAgICBzYW1wbGVfbmFtZXMgIDwtIGg1cmVhZChmaWxlLCAic2FtcGxlcyIpCiAgICB2aWV3X25hbWVzIDwtIG5hbWVzKGZlYXR1cmVfbmFtZXMpCiAgICBncm91cF9uYW1lcyA8LSBuYW1lcyhzYW1wbGVfbmFtZXMpCiAgICBoNWxzLm91dCA8LSBoNWxzLm91dFtncmVwKCJ2YXJpYW5jZV9leHBsYWluZWQiLCBoNWxzLm91dCRuYW1lLCBpbnZlcnQgPSBUUlVFKSxdCiAgfQogIGlmKCJjb3ZhcmlhdGVzIiAlaW4lICBoNWxzLm91dCRuYW1lKXsKICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoIGg1cmVhZChmaWxlLCAiY292YXJpYXRlcyIpW1sxXV0pCiAgfSBlbHNlIHsKICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBOVUxMCiAgfQoKICAjIExvYWQgdHJhaW5pbmcgZGF0YSAoYXMgbmVzdGVkIGxpc3Qgb2YgbWF0cmljZXMpCiAgZGF0YSA8LSBsaXN0KCk7IGludGVyY2VwdHMgPC0gbGlzdCgpCiAgaWYgKGxvYWRfZGF0YSAmJiAiZGF0YSIlaW4laDVscy5vdXQkbmFtZSkgewogICAgCiAgICBvYmplY3RAZGF0YV9vcHRpb25zW1sibG9hZGVkIl1dIDwtIFRSVUUKICAgIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIGRhdGEuLi4iKQogICAgCiAgICBmb3IgKG0gaW4gdmlld19uYW1lcykgewogICAgICBkYXRhW1ttXV0gPC0gbGlzdCgpCiAgICAgIGludGVyY2VwdHNbW21dXSA8LSBsaXN0KCkKICAgICAgZm9yIChnIGluIGdyb3VwX25hbWVzKSB7CiAgICAgICAgaWYgKG9uX2Rpc2spIHsKICAgICAgICAgICMgYXMgRGVsYXllZEFycmF5cwogICAgICAgICAgZGF0YVtbbV1dW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImRhdGEvJXMvJXMiLCBtLCBnKSApICkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgIyBhcyBtYXRyaWNlcwogICAgICAgICAgZGF0YVtbbV1dW1tnXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoImRhdGEvJXMvJXMiLCBtLCBnKSApCiAgICAgICAgICB0cnlDYXRjaChpbnRlcmNlcHRzW1ttXV1bW2ddXSA8LSBhcy5udW1lcmljKCBoNXJlYWQoZmlsZSwgc3ByaW50ZigiaW50ZXJjZXB0cy8lcy8lcyIsIG0sIGcpICkgKSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7IE5VTEwgfSkKICAgICAgICB9CiAgICAgICAgIyBSZXBsYWNlIE5hTiBieSBOQQogICAgICAgIGRhdGFbW21dXVtbZ11dW2lzLm5hbihkYXRhW1ttXV1bW2ddXSldIDwtIE5BICMgdGhpcyByZWFsaXNlZCBpbnRvIG1lbW9yeSwgVE8gRklYCiAgICAgIH0KICAgIH0KICAgIAogICMgQ3JlYXRlIGVtcHR5IHRyYWluaW5nIGRhdGEgKGFzIG5lc3RlZCBsaXN0IG9mIGVtcHR5IG1hdHJpY2VzLCB3aXRoIHRoZSBjb3JyZWN0IGRpbWVuc2lvbnMpCiAgfSBlbHNlIHsKICAgIAogICAgb2JqZWN0QGRhdGFfb3B0aW9uc1tbImxvYWRlZCJdXSA8LSBGQUxTRQogICAgCiAgICBmb3IgKG0gaW4gdmlld19uYW1lcykgewogICAgICBkYXRhW1ttXV0gPC0gbGlzdCgpCiAgICAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICAgICAgIGRhdGFbW21dXVtbZ11dIDwtIC5jcmVhdGVfbWF0cml4X3BsYWNlaG9sZGVyKHJvd25hbWVzID0gZmVhdHVyZV9uYW1lc1tbbV1dLCBjb2xuYW1lcyA9IHNhbXBsZV9uYW1lc1tbZ11dKQogICAgICB9CiAgICB9CiAgfQoKICBvYmplY3RAZGF0YSA8LSBkYXRhCiAgb2JqZWN0QGludGVyY2VwdHMgPC0gaW50ZXJjZXB0cwoKCiAgIyBMb2FkIG1ldGFkYXRhIGlmIGFueQogIGlmICgic2FtcGxlc19tZXRhZGF0YSIgJWluJSBoNWxzLm91dCRuYW1lKSB7CiAgICBvYmplY3RAc2FtcGxlc19tZXRhZGF0YSA8LSBiaW5kX3Jvd3MobGFwcGx5KGdyb3VwX25hbWVzLCBmdW5jdGlvbihnKSBhcy5kYXRhLmZyYW1lKGg1cmVhZChmaWxlLCBzcHJpbnRmKCJzYW1wbGVzX21ldGFkYXRhLyVzIiwgZykpKSkpCiAgfQogIGlmICgiZmVhdHVyZXNfbWV0YWRhdGEiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgb2JqZWN0QGZlYXR1cmVzX21ldGFkYXRhIDwtIGJpbmRfcm93cyhsYXBwbHkodmlld19uYW1lcywgZnVuY3Rpb24obSkgYXMuZGF0YS5mcmFtZShoNXJlYWQoZmlsZSwgc3ByaW50ZigiZmVhdHVyZXNfbWV0YWRhdGEvJXMiLCBtKSkpKSkKICB9CiAgCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAjIyBMb2FkIHNhbXBsZSBjb3ZhcmlhdGVzICMjCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAKICAjIGlmIChhbnkoZ3JlcGwoImNvdl9zYW1wbGVzIiwgaDVscy5vdXQkZ3JvdXApKSl7CiAgIyAgIGNvdmFyaWF0ZXMgPC0gbGlzdCgpCiAgIyAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgIGNvdmFyaWF0ZXNbW2ddXSA8LSBEZWxheWVkQXJyYXk6OkRlbGF5ZWRBcnJheSggSERGNUFycmF5U2VlZChmaWxlLCBuYW1lID0gc3ByaW50ZigiY292X3NhbXBsZXMvJXMiLCBnKSApICkKICAjICAgICB9IGVsc2UgewogICMgICAgICAgIyBhcyBtYXRyaWNlcwogICMgICAgICAgY292YXJpYXRlc1tbZ11dIDwtIGg1cmVhZChmaWxlLCBzcHJpbnRmKCJjb3Zfc2FtcGxlcy8lcyIsIGcpICkKICAjICAgICB9ICAgIAogICMgICB9CiAgIyB9IGVsc2UgY292YXJpYXRlcyA8LSBOVUxMCiAgIyBvYmplY3RAY292YXJpYXRlcyA8LSBjb3ZhcmlhdGVzCgogICMgaWYgKGFueShncmVwbCgiY292X3NhbXBsZXNfdHJhbnNmb3JtZWQiLCBoNWxzLm91dCRncm91cCkpKXsKICAjICAgY292YXJpYXRlc193YXJwZWQgPC0gbGlzdCgpCiAgIyAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgIGNvdmFyaWF0ZXNfd2FycGVkW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkLyVzIiwgZykgKSApCiAgIyAgICAgfSBlbHNlIHsKICAjICAgICAgICMgYXMgbWF0cmljZXMKICAjICAgICAgIGNvdmFyaWF0ZXNfd2FycGVkW1tnXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkLyVzIiwgZykgKQogICMgICAgIH0gICAgCiAgIyAgIH0KICAjIH0gZWxzZSBjb3ZhcmlhdGVzX3dhcnBlZCA8LSBOVUxMCiAgIyBvYmplY3RAY292YXJpYXRlc193YXJwZWQgPC0gY292YXJpYXRlc193YXJwZWQKICAKICAjICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAjIyBMb2FkIGludGVycG9sYXRlZCBmYWN0b3IgdmFsdWVzICMjCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpbnRlcnBvbGF0ZWRfWiA8LSBsaXN0KCkKICAjIGlmIChpc1RSVUUobG9hZF9pbnRlcnBvbF9aKSkgewogICMgICAKICAjICAgaWYgKGlzVFJVRSh2ZXJib3NlKSkgbWVzc2FnZSgiTG9hZGluZyBpbnRlcnBvbGF0ZWQgZmFjdG9yIHZhbHVlcy4uLiIpCiAgIyAgIAogICMgICBmb3IgKGcgaW4gZ3JvdXBfbmFtZXMpIHsKICAjICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dIDwtIGxpc3QoKQogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgICMgaW50ZXJwb2xhdGVkX1pbW2ddXSA8LSBEZWxheWVkQXJyYXk6OkRlbGF5ZWRBcnJheSggSERGNUFycmF5U2VlZChmaWxlLCBuYW1lID0gc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcyIsIGcpICkgKQogICMgICAgIH0gZWxzZSB7CiAgIyAgICAgICAjIGFzIG1hdHJpY2VzCiAgIyAgICAgICB0cnlDYXRjaCggewogICMgICAgICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dW1sibWVhbiJdXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcy9tZWFuIiwgZykgKQogICMgICAgICAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJQcmVkaWNpdGlvbnMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICAgIHRyeUNhdGNoKCB7CiAgIyAgICAgICAgIGludGVycG9sYXRlZF9aW1tnXV1bWyJ2YXJpYW5jZSJdXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcy92YXJpYW5jZSIsIGcpICkKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiVmFyaWFuY2Ugb2YgcHJlZGljdGlvbnMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICAgIHRyeUNhdGNoKCB7CiAgIyAgICAgICAgIGludGVycG9sYXRlZF9aW1tnXV1bWyJuZXdfdmFsdWVzIl1dIDwtIGg1cmVhZChmaWxlLCAiWl9wcmVkaWN0aW9ucy9uZXdfdmFsdWVzIikKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiTmV3IHZhbHVlcyBvZiBaIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQogICMgICAgIH0KICAjICAgfQogICMgfQogICMgb2JqZWN0QGludGVycG9sYXRlZF9aIDwtIGludGVycG9sYXRlZF9aCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIGV4cGVjdGF0aW9ucyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGV4cGVjdGF0aW9ucyA8LSBsaXN0KCkKICBub2RlX25hbWVzIDwtIGg1bHMub3V0W2g1bHMub3V0JGdyb3VwPT0iL2V4cGVjdGF0aW9ucyIsIm5hbWUiXQoKICBpZiAodmVyYm9zZSkgbWVzc2FnZShwYXN0ZTAoIkxvYWRpbmcgZXhwZWN0YXRpb25zIGZvciAiLCBsZW5ndGgobm9kZV9uYW1lcyksICIgbm9kZXMuLi4iKSkKCiAgaWYgKCJBbHBoYVciICVpbiUgbm9kZV9uYW1lcykKICAgIGV4cGVjdGF0aW9uc1tbIkFscGhhVyJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9BbHBoYVciKVt2aWV3X25hbWVzXQogIGlmICgiQWxwaGFaIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJBbHBoYVoiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvQWxwaGFaIilbZ3JvdXBfbmFtZXNdCiAgaWYgKCJTaWdtYSIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siU2lnbWEiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvU2lnbWEiKQogIGlmICgiWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siWiJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9aIilbZ3JvdXBfbmFtZXNdCiAgaWYgKCJXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJXIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1ciKVt2aWV3X25hbWVzXQogIGlmICgiVGhldGFXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJUaGV0YVciXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvVGhldGFXIilbdmlld19uYW1lc10KICBpZiAoIlRoZXRhWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siVGhldGFaIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RoZXRhWiIpW2dyb3VwX25hbWVzXQogICMgaWYgKCJUYXUiICVpbiUgbm9kZV9uYW1lcykKICAjICAgZXhwZWN0YXRpb25zW1siVGF1Il1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RhdSIpCiAgCiAgb2JqZWN0QGV4cGVjdGF0aW9ucyA8LSBleHBlY3RhdGlvbnMKCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCBtb2RlbCBvcHRpb25zICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIG1vZGVsIG9wdGlvbnMuLi4iKQoKICB0cnlDYXRjaCggewogICAgb2JqZWN0QG1vZGVsX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ21vZGVsX29wdGlvbnMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIk1vZGVsIG9wdGlvbnMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCgogICMgQ29udmVydCBUcnVlL0ZhbHNlIHN0cmluZ3MgdG8gbG9naWNhbCB2YWx1ZXMKICBmb3IgKGkgaW4gbmFtZXMob2JqZWN0QG1vZGVsX29wdGlvbnMpKSB7CiAgICBpZiAob2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPT0gIkZhbHNlIiB8fCBvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSA9PSAiVHJ1ZSIpIHsKICAgICAgb2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPC0gYXMubG9naWNhbChvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSkKICAgIH0gZWxzZSB7CiAgICAgIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldIDwtIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldCiAgICB9CiAgfQoKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIHRyYWluaW5nIG9wdGlvbnMgYW5kIHN0YXRpc3RpY3MgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCiAgaWYgKHZlcmJvc2UpIG1lc3NhZ2UoIkxvYWRpbmcgdHJhaW5pbmcgb3B0aW9ucyBhbmQgc3RhdGlzdGljcy4uLiIpCgogICMgTG9hZCB0cmFpbmluZyBvcHRpb25zCiAgaWYgKGxlbmd0aChvYmplY3RAdHJhaW5pbmdfb3B0aW9ucykgPT0gMCkgewogICAgdHJ5Q2F0Y2goIHsKICAgICAgb2JqZWN0QHRyYWluaW5nX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX29wdHMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiVHJhaW5pbmcgb3B0cyBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICB9CgogICMgTG9hZCB0cmFpbmluZyBzdGF0aXN0aWNzCiAgdHJ5Q2F0Y2goIHsKICAgIG9iamVjdEB0cmFpbmluZ19zdGF0cyA8LSBoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX3N0YXRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkKICAgIG9iamVjdEB0cmFpbmluZ19zdGF0cyA8LSBoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX3N0YXRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIlRyYWluaW5nIHN0YXRzIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQoKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgY292YXJpYXRlcyBvcHRpb25zICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIAogICMgaWYgKGFueShncmVwbCgiY292X3NhbXBsZXMiLCBoNWxzLm91dCRncm91cCkpKSB7IAogICMgICBpZiAoaXNUUlVFKHZlcmJvc2UpKSBtZXNzYWdlKCJMb2FkaW5nIGNvdmFyaWF0ZXMgb3B0aW9ucy4uLiIpCiAgIyAgIHRyeUNhdGNoKCB7CiAgIyAgICAgb2JqZWN0QG1lZmlzdG9fb3B0aW9ucyA8LSBhcy5saXN0KGg1cmVhZChmaWxlLCAnc21vb3RoX29wdHMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICAjICAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJDb3ZhcmlhdGVzIG9wdGlvbnMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCiAgIyAgIAogICMgICAjIENvbnZlcnQgVHJ1ZS9GYWxzZSBzdHJpbmdzIHRvIGxvZ2ljYWwgdmFsdWVzCiAgIyAgIGZvciAoaSBpbiBuYW1lcyhvYmplY3RAbWVmaXN0b19vcHRpb25zKSkgewogICMgICAgIGlmIChvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldID09ICJGYWxzZSIgfCBvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldID09ICJUcnVlIikgewogICMgICAgICAgb2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSA8LSBhcy5sb2dpY2FsKG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0pCiAgIyAgICAgfSBlbHNlIHsKICAjICAgICAgIG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0gPC0gb2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXQogICMgICAgIH0KICAjICAgfQogICMgICAKICAjIH0KICAjIAogIAogICAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCB2YXJpYW5jZSBleHBsYWluZWQgZXN0aW1hdGVzICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgCiAgaWYgKCJ2YXJpYW5jZV9leHBsYWluZWQiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgcjJfbGlzdCA8LSBsaXN0KAogICAgICByMl90b3RhbCA9IGg1cmVhZChmaWxlLCAidmFyaWFuY2VfZXhwbGFpbmVkL3IyX3RvdGFsIilbZ3JvdXBfbmFtZXNdLAogICAgICByMl9wZXJfZmFjdG9yID0gaDVyZWFkKGZpbGUsICJ2YXJpYW5jZV9leHBsYWluZWQvcjJfcGVyX2ZhY3RvciIpW2dyb3VwX25hbWVzXQogICAgKQogICAgb2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dIDwtIHIyX2xpc3QKICB9CiAgCiAgIyBIYWNrIHRvIGZpeCB0aGUgcHJvYmxlbXMgd2hlcmUgdmFyaWFuY2UgZXhwbGFpbmVkIHZhbHVlcyByYW5nZSBmcm9tIDAgdG8gMSAoJSkKICBpZiAobWF4KHNhcHBseShvYmplY3RAY2FjaGUkdmFyaWFuY2VfZXhwbGFpbmVkJHIyX3RvdGFsLG1heCxuYS5ybT1UUlVFKSxuYS5ybT1UUlVFKTwxKSB7CiAgICBmb3IgKG0gaW4gMTpsZW5ndGgodmlld19uYW1lcykpIHsKICAgICAgZm9yIChnIGluIDE6bGVuZ3RoKGdyb3VwX25hbWVzKSkgewogICAgICAgIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfdG90YWxbW2ddXVtbbV1dIDwtIDEwMCAqIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfdG90YWxbW2ddXVtbbV1dCiAgICAgICAgb2JqZWN0QGNhY2hlJHZhcmlhbmNlX2V4cGxhaW5lZCRyMl9wZXJfZmFjdG9yW1tnXV1bLG1dIDwtIDEwMCAqIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfcGVyX2ZhY3RvcltbZ11dWyxtXQogICAgICB9CiAgICB9CiAgfQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIFNwZWNpZnkgZGltZW5zaW9uYWxpdGllcyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogIAogICMgU3BlY2lmeSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgZGF0YQogIG9iamVjdEBkaW1lbnNpb25zW1siTSJdXSA8LSBsZW5ndGgoZGF0YSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2Ygdmlld3MKICBvYmplY3RAZGltZW5zaW9uc1tbIkciXV0gPC0gbGVuZ3RoKGRhdGFbWzFdXSkgICAgICAgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIGdyb3VwcwogIG9iamVjdEBkaW1lbnNpb25zW1siTiJdXSA8LSBzYXBwbHkoZGF0YVtbMV1dLCBuY29sKSAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2Ygc2FtcGxlcyAocGVyIGdyb3VwKQogIG9iamVjdEBkaW1lbnNpb25zW1siRCJdXSA8LSBzYXBwbHkoZGF0YSwgZnVuY3Rpb24oZSkgbnJvdyhlW1sxXV0pKSAgIyBudW1iZXIgb2YgZmVhdHVyZXMgKHBlciB2aWV3KQogICMgb2JqZWN0QGRpbWVuc2lvbnNbWyJDIl1dIDwtIG5yb3coY292YXJpYXRlc1tbMV1dKSAgICAgICAgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIGNvdmFyaWF0ZXMKICBvYmplY3RAZGltZW5zaW9uc1tbIksiXV0gPC0gbmNvbChvYmplY3RAZXhwZWN0YXRpb25zJFpbWzFdXSkgICAgICAgICMgbnVtYmVyIG9mIGZhY3RvcnMKICAKICAjIEFzc2lnbiBzYW1wbGUgYW5kIGZlYXR1cmUgbmFtZXMgKHNsb3cgZm9yIGxhcmdlIG1hdHJpY2VzKQogIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJBc3NpZ25pbmcgbmFtZXMgdG8gdGhlIGRpZmZlcmVudCBkaW1lbnNpb25zLi4uIikKCiAgIyBDcmVhdGUgZGVmYXVsdCBmZWF0dXJlcyBuYW1lcyBpZiB0aGV5IGFyZSBudWxsCiAgaWYgKGlzLm51bGwoZmVhdHVyZV9uYW1lcykpIHsKICAgIHByaW50KCJGZWF0dXJlcyBuYW1lcyBub3QgZm91bmQsIGdlbmVyYXRpbmcgZGVmYXVsdDogZmVhdHVyZTFfdmlldzEsIC4uLiwgZmVhdHVyZURfdmlld00iKQogICAgZmVhdHVyZV9uYW1lcyA8LSBsYXBwbHkoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIk0iXV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24obSkgc3ByaW50ZigiZmVhdHVyZSVkX3ZpZXdfJmQiLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIkQiXV1bbV0pKSwgbSkpCiAgfSBlbHNlIHsKICAgICMgQ2hlY2sgZHVwbGljYXRlZCBmZWF0dXJlcyBuYW1lcwogICAgYWxsX25hbWVzIDwtIHVubmFtZSh1bmxpc3QoZmVhdHVyZV9uYW1lcykpCiAgICBkdXBsaWNhdGVkX25hbWVzIDwtIHVuaXF1ZShhbGxfbmFtZXNbZHVwbGljYXRlZChhbGxfbmFtZXMpXSkKICAgIGlmIChsZW5ndGgoZHVwbGljYXRlZF9uYW1lcyk+MCkgCiAgICAgIHdhcm5pbmcoIlRoZXJlIGFyZSBkdXBsaWNhdGVkIGZlYXR1cmVzIG5hbWVzIGFjcm9zcyBkaWZmZXJlbnQgdmlld3MuIFdlIHdpbGwgYWRkIHRoZSBzdWZmaXggKl92aWV3KiBvbmx5IGZvciB0aG9zZSBmZWF0dXJlcyAKICAgICAgICAgICAgRXhhbXBsZTogaWYgeW91IGhhdmUgYm90aCBUUDUzIGluIG1STkEgYW5kIG11dGF0aW9uIGRhdGEgaXQgd2lsbCBiZSByZW5hbWVkIHRvIFRQNTNfbVJOQSwgVFA1M19tdXRhdGlvbiIpCiAgICBmb3IgKG0gaW4gbmFtZXMoZmVhdHVyZV9uYW1lcykpIHsKICAgICAgdG1wIDwtIHdoaWNoKGZlYXR1cmVfbmFtZXNbW21dXSAlaW4lIGR1cGxpY2F0ZWRfbmFtZXMpCiAgICAgIGlmIChsZW5ndGgodG1wKT4wKSBmZWF0dXJlX25hbWVzW1ttXV1bdG1wXSA8LSBwYXN0ZShmZWF0dXJlX25hbWVzW1ttXV1bdG1wXSwgbSwgc2VwPSJfIikKICAgIH0KICB9CiAgZmVhdHVyZXNfbmFtZXMob2JqZWN0KSA8LSBmZWF0dXJlX25hbWVzCiAgCiAgIyBDcmVhdGUgZGVmYXVsdCBzYW1wbGVzIG5hbWVzIGlmIHRoZXkgYXJlIG51bGwKICBpZiAoaXMubnVsbChzYW1wbGVfbmFtZXMpKSB7CiAgICBwcmludCgiU2FtcGxlcyBuYW1lcyBub3QgZm91bmQsIGdlbmVyYXRpbmcgZGVmYXVsdDogc2FtcGxlMSwgLi4uLCBzYW1wbGVOIikKICAgIHNhbXBsZV9uYW1lcyA8LSBsYXBwbHkob2JqZWN0QGRpbWVuc2lvbnNbWyJOIl1dLCBmdW5jdGlvbihuKSBwYXN0ZTAoInNhbXBsZSIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG4pKSkpCiAgfQogIHNhbXBsZXNfbmFtZXMob2JqZWN0KSA8LSBzYW1wbGVfbmFtZXMKCiAgIyBBZGQgY292YXJpYXRlcyBuYW1lcwogICMgaWYoIWlzLm51bGwob2JqZWN0QGNvdmFyaWF0ZXMpKXsKICAjICAgIyBDcmVhdGUgZGVmYXVsdCBjb3ZhcmlhdGVzIG5hbWVzIGlmIHRoZXkgYXJlIG51bGwKICAjICAgaWYgKGlzLm51bGwoY292YXJpYXRlX25hbWVzKSkgewogICMgICAgIHByaW50KCJDb3ZhcmlhdGUgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IGNvdmFyaWF0ZTEsIC4uLiwgY292YXJpYXRlQyIpCiAgIyAgICAgY292YXJpYXRlX25hbWVzIDwtIHBhc3RlMCgic2FtcGxlIiwgYXMuY2hhcmFjdGVyKHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJDIl1dKSkpCiAgIyAgIH0KICAjICAgY292YXJpYXRlc19uYW1lcyhvYmplY3QpIDwtIGNvdmFyaWF0ZV9uYW1lcwogICMgfQogIAogICMgU2V0IHZpZXdzIG5hbWVzCiAgaWYgKGlzLm51bGwobmFtZXMob2JqZWN0QGRhdGEpKSkgewogICAgcHJpbnQoIlZpZXdzIG5hbWVzIG5vdCBmb3VuZCwgZ2VuZXJhdGluZyBkZWZhdWx0OiB2aWV3MSwgLi4uLCB2aWV3TSIpCiAgICB2aWV3X25hbWVzIDwtIHBhc3RlMCgidmlldyIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siTSJdXSkpKQogIH0KICB2aWV3c19uYW1lcyhvYmplY3QpIDwtIHZpZXdfbmFtZXMKICAKICAjIFNldCBncm91cHMgbmFtZXMKICBpZiAoaXMubnVsbChuYW1lcyhvYmplY3RAZGF0YVtbMV1dKSkpIHsKICAgIHByaW50KCJHcm91cHMgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IGdyb3VwMSwgLi4uLCBncm91cEciKQogICAgZ3JvdXBfbmFtZXMgPC0gcGFzdGUwKCJncm91cCIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siRyJdXSkpKQogIH0KICBncm91cHNfbmFtZXMob2JqZWN0KSA8LSBncm91cF9uYW1lcwogIAogICMgU2V0IGZhY3RvcnMgbmFtZXMKICBmYWN0b3JzX25hbWVzKG9iamVjdCkgIDwtIHBhc3RlMCgiRmFjdG9yIiwgYXMuY2hhcmFjdGVyKHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJLIl1dKSkpCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIFBhcnNlIGZhY3RvcnMgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjCiAgCiAgIyBDYWxjdWxhdGUgdmFyaWFuY2UgZXhwbGFpbmVkIGVzdGltYXRlcyBwZXIgZmFjdG9yCiAgaWYgKGlzLm51bGwob2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dKSkgewogICAgb2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dIDwtIGNhbGN1bGF0ZV92YXJpYW5jZV9leHBsYWluZWQob2JqZWN0KQogIH0gCiAgCiAgIyBSZW1vdmUgaW5hY3RpdmUgZmFjdG9ycwogIGlmIChyZW1vdmVfaW5hY3RpdmVfZmFjdG9ycykgewogICAgcjIgPC0gcm93U3Vtcyhkby5jYWxsKCdjYmluZCcsIGxhcHBseShvYmplY3RAY2FjaGVbWyJ2YXJpYW5jZV9leHBsYWluZWQiXV0kcjJfcGVyX2ZhY3Rvciwgcm93U3VtcywgbmEucm09VFJVRSkpKQogICAgdmFyLnRocmVzaG9sZCA8LSAwLjAwMDEKICAgIGlmIChhbGwocjIgPCB2YXIudGhyZXNob2xkKSkgewogICAgICB3YXJuaW5nKHNwcmludGYoIkFsbCAlcyBmYWN0b3JzIHdlcmUgZm91bmQgdG8gZXhwbGFpbiBsaXR0bGUgb3Igbm8gdmFyaWFuY2Ugc28gcmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMgb3B0aW9uIGhhcyBiZWVuIGRpc2FibGVkLiIsIGxlbmd0aChyMikpKQogICAgfSBlbHNlIGlmIChhbnkocjIgPCB2YXIudGhyZXNob2xkKSkgewogICAgICBvYmplY3QgPC0gc3Vic2V0X2ZhY3RvcnMob2JqZWN0LCB3aGljaChyMj49dmFyLnRocmVzaG9sZCkpCiAgICAgIG1lc3NhZ2Uoc3ByaW50ZigiJXMgZmFjdG9ycyB3ZXJlIGZvdW5kIHRvIGV4cGxhaW4gbm8gdmFyaWFuY2UgYW5kIHRoZXkgd2VyZSByZW1vdmVkIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLiBZb3UgY2FuIGRpc2FibGUgdGhpcyBvcHRpb24gYnkgc2V0dGluZyBsb2FkX21vZGVsKC4uLiwgcmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMgPSBGQUxTRSkiLCBzdW0ocjIgPCB2YXIudGhyZXNob2xkKSkpCiAgICB9CiAgfQogIAogICMgW0RvbmUgaW4gbW9mYXB5Ml0gU29ydCBmYWN0b3JzIGJ5IHRvdGFsIHZhcmlhbmNlIGV4cGxhaW5lZAogIGlmIChzb3J0X2ZhY3RvcnMgJiYgb2JqZWN0QGRpbWVuc2lvbnMkSz4xKSB7CgogICAgIyBTYW5pdHkgY2hlY2tzCiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiUmUtb3JkZXJpbmcgZmFjdG9ycyBieSB0aGVpciB2YXJpYW5jZSBleHBsYWluZWQuLi4iKQoKICAgICMgQ2FsY3VsYXRlIHZhcmlhbmNlIGV4cGxhaW5lZCBwZXIgZmFjdG9yIGFjcm9zcyBhbGwgdmlld3MKICAgIHIyIDwtIHJvd1N1bXMoc2FwcGx5KG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSRyMl9wZXJfZmFjdG9yLCBmdW5jdGlvbihlKSByb3dTdW1zKGUsIG5hLnJtID0gVFJVRSkpKQogICAgb3JkZXJfZmFjdG9ycyA8LSBjKG5hbWVzKHIyKVtvcmRlcihyMiwgZGVjcmVhc2luZyA9IFRSVUUpXSkKCiAgICAjIHJlLW9yZGVyIGZhY3RvcnMKICAgIG9iamVjdCA8LSBzdWJzZXRfZmFjdG9ycyhvYmplY3QsIG9yZGVyX2ZhY3RvcnMpCiAgfQoKICAjIE1hc2sgb3V0bGllcnMKICBpZiAocmVtb3ZlX291dGxpZXJzKSB7CiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiUmVtb3Zpbmcgb3V0bGllcnMuLi4iKQogICAgb2JqZWN0IDwtIC5kZXRlY3Rfb3V0bGllcnMob2JqZWN0KQogIH0KICAKICAjIE1hc2sgaW50ZXJjZXB0cyBmb3Igbm9uLUdhdXNzaWFuIGRhdGEKICBpZiAoYW55KG9iamVjdEBtb2RlbF9vcHRpb25zJGxpa2VsaWhvb2RzIT0iZ2F1c3NpYW4iKSkgewogICAgZm9yIChtIGluIG5hbWVzKHdoaWNoKG9iamVjdEBtb2RlbF9vcHRpb25zJGxpa2VsaWhvb2RzIT0iZ2F1c3NpYW4iKSkpIHsKICAgICAgZm9yIChnIGluIG5hbWVzKG9iamVjdEBpbnRlcmNlcHRzW1ttXV0pKSB7CiAgICAgICAgb2JqZWN0QGludGVyY2VwdHNbW21dXVtbZ11dIDwtIE5BCiAgICAgIH0KICAgIH0KICB9CgogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgIyMgUXVhbGl0eSBjb250cm9scyAjIwogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpZiAodmVyYm9zZSkgbWVzc2FnZSgiRG9pbmcgcXVhbGl0eSBjb250cm9sLi4uIikKICAjIG9iamVjdCA8LSAucXVhbGl0eV9jb250cm9sKG9iamVjdCwgdmVyYm9zZSA9IHZlcmJvc2UpCiAgIyAKICByZXR1cm4ob2JqZWN0KQp9Cgptb2ZhX3RyYWluZWQgPC0gbG9hZF9tb2RlbChvdXRmaWxlKQoKc2FtcGxlc19uYW1lcyhtb2ZhX3RyYWluZWQpIDwtIHNhbXBsZXNfbmFtZXMob2JqZWN0KQpzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkKcm93bmFtZXMoc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpKSA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZClbWyJzYW1wbGUiXV0KCgpgYGAKCiMjIyBWaXN1YWxpemUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGZhY3RvcnMKCmBgYHtyfQpwbG90X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIHg9J2ZhY3RvcicsIHk9J2dyb3VwJywgc3BsaXRfYnkgPSAndmlldycsIHBsb3RfdG90YWwgPSBUUlVFLCBtYXhfcjIgPSA1MClbWzFdXSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKQoKZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMl1dICU+JQogIGdncGxvdChhZXMoZ3JvdXAsIHZhbHVlKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgeWxhYigiVmFyLiAoJSkiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpCmBgYAoKUGxvdCBieSBjZWxsdHlwZQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQpnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgZ2dwbG90KGFlcyhmYWN0b3IsIHZhbHVlKSkgKyBnZW9tX2NvbCgpICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X3dyYXAoZ3JvdXB+LiwgbmNvbCA9IDYsIHNjYWxlcyA9ICJmcmVlX3giKQpgYGAKCmBgYHtyfQpwbG90X2ZhY3Rvcl9jb3IobW9mYV90cmFpbmVkLCBtZXRob2QgPSAic3BlYXJtYW4iKQpgYGAKCgpgYGB7cn0KIyMgQ29ycmVsYXRpb24gd2l0aCBwcmluY2lwYWwgY29tcG9uZW50cwpwY3MgPC0gcmVkdWNlZERpbShzY2UpCmZjdHJzIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCkgJT4lCiAgcHVycnI6OnJlZHVjZShyYmluZCkKCmNvcnJwbG90Ojpjb3JycGxvdChjb3IocGNzLCBmY3Ryc1tyb3duYW1lcyhwY3MpLF0pKQpgYGAKCiMjIyMgRmFjdG9yIElEIHBsb3RzCgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpwbG90X2ZhY3Rvcl9vcmRlcmVkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZil7CiAgZmFjdG9yX2RmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICAgICAgbXV0YXRlKG9yZ2FuID0gc2FwcGx5KHN0cl9zcGxpdChzYW1wbGUsICJfIiksIGZ1bmN0aW9uKHgpIHhbMl0pKSAlPiUKICAgICAgZ3JvdXBfYnkoZ3JvdXApICU+JQogICAgICBtdXRhdGUoZ3JfbWVhbiA9IG1lZGlhbih2YWx1ZSkpICU+JQogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIGFycmFuZ2UoZ3JfbWVhbikgJT4lCiAgICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgCiAgCiAgcjJfZGYgPC0gZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUKICAgIGZpbHRlcihmYWN0b3I9PXBhc3RlMCgnRmFjdG9yJyxmKSkgJT4lCiAgICBtdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHMgPSBsZXZlbHMoZmFjdG9yX2RmJGdyb3VwKSkpCiAgCiAgcGwxIDwtIGZhY3Rvcl9kZiAlPiUKICAgICAgZ2dwbG90KGFlcyhncm91cCwgdmFsdWUpKSArCiAgICAgIGdlb21fYm94cGxvdCgpICsKICAgICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG9yPSBvcmdhbiksIHNpemU9MC43KSArCiAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPTIpICsKICAgICAgY29vcmRfZmxpcCgpICsKICAgICAgeWxhYihwYXN0ZTAoIkZhY3RvciAiLCBmKSkgKwogICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKICAKICBwbDIgPC0gcjJfZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKGdyb3VwLCB2YWx1ZSkpICsKICAgIGdlb21fY29sKCkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHlsYWIoIiUgdmFyaWFuY2UgZXhwbGFpbmVkIikgKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICAgIHJlbW92ZV95X2F4aXMoKQogIAogIHBsMSArIHBsMiArIHBsb3RfbGF5b3V0KHdpZHRocz1jKDIsMSksIGd1aWRlcz0iY29sbGVjdCIpIAp9CgpnZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsKICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JQogICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAKICAgICMgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzID0gKSkKICB0b3BfcXVhbnRfcjIgPC0gcXVhbnRpbGUocjJfZGYkdmFsdWUsIHByb2JzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSlbIjgwJSJdCiAgdG9wX2dyb3VwcyA8LSByMl9kZiRncm91cFtyMl9kZiR2YWx1ZSA+PSB0b3BfcXVhbnRfcjJdCiAgcmV0dXJuKHRvcF9ncm91cHMpCn0KCnNhdmVfZmFjdG9yX2lkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZiwgZmlnZGlyKXsKICAjIyBPcmRlciBjZWxsdHlwZXMgYnkgZmFjdG9yIHZhbHVlcwogIHAxIDwtIHBsb3RfZmFjdG9yX29yZGVyZWQobW9mYV90cmFpbmVkLCBmKQogIAogICMjIFBsb3QgZmFjdG9yIHZhbHVlcyBhY3Jvc3Mgb3JnYW5zIGZvciBjZWxsdHlwZXMgd2l0aCBoaWdoIHZhcmlhbmNlIGV4cGxhaW5lZAogIHAyIDwtIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGdyb3VwcyA9IGdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGYpLCBncm91cF9ieSA9ICJncm91cCIsIAogICAgICAgICAgICAgIGNvbG9yX2J5ID0gIm9yZ2FuIiwgCiAgICAgICAgICAgICAgZG90X3NpemUgPSAyLCBkb2RnZSA9IFRSVUUKICAgICAgICAgICAgICApCiAgCiAgIyMgUGxvdCBmYWN0b3Igd2VpZ2h0cyBvbiBnZW5lcwogICMgcGxvdF9kYXRhX2hlYXRtYXAobW9mYV90cmFpbmVkLCBmYWN0b3IgPSBmLCBuZmVhdHVyZXMgPSA1MCwgdGV4dF9zaXplID0gMywgc2hvd19jb2xuYW1lcz1GQUxTRSwKICAjICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fc2FtcGxlcyA9IGMoIm9yZ2FuIiwgInRpbWUiLCAibWV0aG9kIiwgImRvbm9yIikpCiAgcDMgPC0gcGxvdF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIG5mZWF0dXJlcyA9IDMwLCB0ZXh0X3NpemUgPSAzKSArCiAgIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMC4xLCAwLjEpKQogIAogIGZ1bGxfcGwgPC0gKHAxIHwgKHAyIC8gcDMpKSArCiAgICBwbG90X2xheW91dChndWlkZXM9ImNvbGxlY3QiKSAKICBnZ3NhdmUoZ2x1ZSgie2ZpZ2Rpcn0vTU9GQV97c3BsaXR9X2ZhY3RvcklEX2ZhY3RvcntmfS5wZGYiKSwgcGxvdD1mdWxsX3BsLCB3aWR0aCA9IDE1LCBoZWlnaHQgPSAxMCkKfQoKZm9yIChmIGluIDE6bW9mYV90cmFpbmVkQGRpbWVuc2lvbnMkSyl7CiAgcHJpbnQocGFzdGUwKCJTYXZpbmcgSUQgZm9yIEZhY3RvciAiLCBmLCAiLi4uIikpCiAgc2F2ZV9mYWN0b3JfaWQobW9mYV90cmFpbmVkLCBmPWYsIGZpZ2RpciA9IGZpZ2RpcikgIAp9CgojIHNhdmVfZmFjdG9yX2lkKG1vZmFfdHJhaW5lZCwgZj0xLCBmaWdkaXIgPSBmaWdkaXIpICAKIyBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgbmZlYXR1cmVzID0gMzAsIHRleHRfc2l6ZSA9IDMpICsKIyAgICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZD1jKDAuMSwgMC4xKSkKYGBgCgoKCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsgLS0+CjwhLS0gICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JSAtLT4KPCEtLSAgICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAlPiUgLS0+CjwhLS0gICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscyA9IGxldmVscyhmYWN0b3JfZGYkZ3JvdXApKSkgLS0+CjwhLS0gICB0b3BfcXVhbnRfcjIgPC0gcXVhbnRpbGUocjJfZGYkdmFsdWUsIHByb2JzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSlbIjgwJSJdIC0tPgo8IS0tICAgdG9wX2dyb3VwcyA8LSByMl9kZiRncm91cFtyMl9kZiR2YWx1ZSA+PSB0b3BfcXVhbnRfcjJdIC0tPgo8IS0tICAgcmV0dXJuKHRvcF9ncm91cHMpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9yPTIsIGdyb3Vwcz1nZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IobW9mYV90cmFpbmVkLCAyKVszOjVdLCBkb2RnZSA9IFRSVUUsIGFkZF9ib3hwbG90ID0gVFJVRSwgY29sb3JfYnk9ImRvbm9yIikgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3I9MiwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGdyb3VwX2J5ID0gIm9yZ2FuIiwgZG9kZ2UgPSBUUlVFLCBhZGRfYm94cGxvdCA9IFRSVUUsIGNvbG9yX2J5PSJvcmdhbiIpICsgLS0+CjwhLS0gICB5bGltKDMsOCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGdyb3Vwcz1nZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IobW9mYV90cmFpbmVkLCAyKVszOjVdLCBmYWN0b3I9MiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh2YWx1ZSwgZmlsbD1vcmdhbikpICsgLS0+CjwhLS0gICBnZW9tX2hpc3RvZ3JhbSgpIC0tPgo8IS0tICAgIyBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKyAtLT4KPCEtLSAgIGdncHVicjo6c3RhdF9jb3IoKSAtLT4KPCEtLSBwbG90X2ZhY3RvcnNfdnNfY292KG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGNvdmFyaWF0ZXMgPSAiIikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSB3IDwtIGdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9ICdhbGwnLCBhcy5kYXRhLmZyYW1lID0gRkFMU0UpIC0tPgo8IS0tIGFzLmRhdGEuZnJhbWUodyRzY2FsZWRfbG9nY291bnRzKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImdlbmUiKSAlPiUgLS0+CjwhLS0gICB3cml0ZV9jc3YoIn4vTU9GQV93ZWlnaHRzLmNzdiIpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIyMgS05OIGdyYXBoIHBlciBjZWxsdHlwZQoKYGBge3J9CiMjIEdldCBmYWN0b3JzIHRoYXQgZXhwbGFpbiBtb3N0IHZhcmlhbmNlIGluIGVhY2ggY2VsbHR5cGUKZ2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZ3IsIG1pbl9SMj0yKXsKICBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgICBmaWx0ZXIoZ3JvdXA9PWdyKSAlPiUKICAgIGZpbHRlcih2YWx1ZSA+PSBtaW5fUjIpICU+JQogICAgcHVsbChmYWN0b3IpICU+JQogICAgYXMuY2hhcmFjdGVyKCkKfQoKIyMgTWFrZSBLTk4gZ3JhcGggYmFzZWQgb24gc2ltaWxhcml0eSBvZiB0b3AgZmFjdG9ycyBmb3IgZWFjaCBjZWxsdHlwZQpnZXRfY3RfS05OX2dyYXBoIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZ3IsIG1pbl9SMj01LCBrPTUpewogICMjIEdldCBmYWN0b3JzIHRoYXQgZXhwbGFpbiBtb3N0IHZhcmlhbmNlIHBlciBjZWxsdHlwZQogIGZzIDwtIGdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZShtb2ZhX3RyYWluZWQsIGdyLCBtaW5fUjIgPSBtaW5fUjIpCiAgCiAgIyMgRXhjbHVkZSBmYWN0b3IxIChwcm9saWZlcmF0aW9uKQogIGZzIDwtIGZzWyFmcyAlaW4lIGMoIkZhY3RvcjEiLCAiRmFjdG9yMiIpXQogIAogICMjIE1ha2UgS05OIGdyYXBoIGZyb20gdG9wIGZhY3RvcnMKICBaIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWdyLCBmYWN0b3JzID0gZnMpW1sxXV0KICBrbm5fY3QgPC0gYnVpbGRLTk5HcmFwaCh0KFopLCBrPWspCiAgCiAgIyMgQWRkIGF0dHJpYnV0ZXMKICBtZXRhZGF0YV9jdCA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZClbcm93bmFtZXMoWiksXQogICMgY292YXJpYXRlcwogIFYoa25uX2N0KSRvcmdhbiA8LSBtZXRhZGF0YV9jdCRvcmdhbgogIFYoa25uX2N0KSRhZ2UgPC0gbWV0YWRhdGFfY3QkYWdlCiAgVihrbm5fY3QpJG5fY2VsbHMgPC0gbWV0YWRhdGFfY3Qkbl9jZWxscwogIFYoa25uX2N0KSRtZXRob2QgPC0gbWV0YWRhdGFfY3QkbWV0aG9kCiAgVihrbm5fY3QpJGRvbm9yIDwtIG1ldGFkYXRhX2N0JGRvbm9yCiAgIyB0b3AgZmFjdG9ycwogIGZvciAoYyBpbiBjb2xuYW1lcyhaKSl7CiAgIHZlcnRleF9hdHRyKGtubl9jdClbW2NdXSA8LSBaWyxjXSAgCiAgfQogIAogIHJldHVybihrbm5fY3QpCiAgfQoKIyMgUGxvdCBLTk4gZ3JhcGgKcGxvdF9jdF9LTk5fZ3JhcGggPC0gZnVuY3Rpb24oa25uLCBjb2xvcl9ieT0ib3JnYW4iKXsKICAjIyBEZWZpbmUgY29sb3IgCiAgaWYgKCFjb2xvcl9ieSAlaW4lIG5hbWVzKHZlcnRleF9hdHRyKGtubikpKXsKICAgIHN0b3AoInNwZWNpZmllZCBjb2xvcl9ieSB2YXJpYWJsZSBpcyBub3QgaW4gdmVydGV4X2F0dHIoa25uKSIpCiAgfQogIAogIGlmIChjb2xvcl9ieT09Im9yZ2FuIil7IAogICAgc2NhbGVfY29sb3Jfa25uZ3JhcGggPC0gc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKQogIH0gZWxzZSBpZiAoaXMubnVtZXJpYyh2ZXJ0ZXhfYXR0cihrbm4sIGNvbG9yX2J5KSkpewogICAgc2NhbGVfY29sb3Jfa25uZ3JhcGggPC0gc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbj0ibWFnbWEiKSAgCiAgfSBlbHNlIHsKICAgICAgc2NhbGVfY29sb3Jfa25uZ3JhcGggPC0gc2NhbGVfY29sb3JfZGlzY3JldGUoKQogICAgfQogIAogIHZlcnRleF9hdHRyKGtubiwgImNvbG9yX2J5IikgPC0gdmVydGV4X2F0dHIoa25uLCBjb2xvcl9ieSkKICAKICBnZ3JhcGgoa25uKSArCiAgICBnZW9tX2VkZ2VfbGluazAoKSArCiAgICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yPWNvbG9yX2J5LCBzaXplPW5fY2VsbHMpKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCArCiAgICBzY2FsZV9zaXplKHJhbmdlPWMoMiw3KSkgCiAgfQoKZ2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlKG1vZmFfdHJhaW5lZCwgIk5LIikKCmFsbF9ncm91cHMgPC0gbmFtZXMoZ2V0X2RhdGEobW9mYV90cmFpbmVkKVtbMV1dKQprbm5fZ3JhcGhfcGwgPC0gbGFwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpewogIGtubiA8LSBnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyKQogIHBsb3RfY3RfS05OX2dyYXBoKGtubiwgY29sb3JfYnkgPSAnb3JnYW4nKSArIGdndGl0bGUoZykKICB9KQoKa25uX2dyYXBoX3BsIDwtIHNldE5hbWVzKGtubl9ncmFwaF9wbCwgYWxsX2dyb3VwcykKa25uIDwtIGdldF9jdF9LTk5fZ3JhcGgobW9mYV90cmFpbmVkLCAnQjEnLCBrPTUsIG1pbl9SMiA9IDEpCnBsb3RfY3RfS05OX2dyYXBoKGtubiwgY29sb3JfYnkgPSAnRmFjdG9yNScpIApgYGAKCmBgYHtyfQpwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsZ3JvdXBzID0gJ01BVFVSRV9CJywgZmFjdG9ycyA9IGMoNSksIGdyb3VwX2J5ID0nb3JnYW4nLCBjb2xvcl9ieSA9ICJhZ2UiKQpgYGAKCgpgYGB7cn0KIyMgU2NvcmUgY29ubmVjdGl2aXR5IGJldHdlZW4gc2FtcGxlcyBmcm9tIHRoZSBzYW1lIG9yZ2FuCi5jYWxjX2Nvbm5lY3Rpdml0eV9zY29yZSA8LSBmdW5jdGlvbihrbm4sIG8pewogIGFkaiA8LSBnZXQuYWRqYWNlbmN5KGtubikKICBuX29yZyA8LSBzdW0oVihrbm4pJG9yZ2FuPT1vKQogIG5fb3RoZXIgPC0gc3VtKFYoa25uKSRvcmdhbiE9bykKICB3aXRoaW5fZWRnZXMgPC0gc3VtKGFkaltWKGtubikkb3JnYW49PW8sVihrbm4pJG9yZ2FuPT1vXSkKICBiZXR3ZWVuX2VkZ2VzIDwtIHN1bShhZGpbVihrbm4pJG9yZ2FuPT1vLFYoa25uKSRvcmdhbiE9b10pCiAgc2NvcmUgPC0gKHdpdGhpbl9lZGdlcy9iZXR3ZWVuX2VkZ2VzKSoobl9vdGhlci9uX29yZykKICByZXR1cm4oc2NvcmUpCiAgfQoKIyMgQ2FsY3VsYXRlIGNvbm5lY3Rpdml0eSBzY29yZSBmb3IgcGVybXV0YXRpb25zIG9mIG5vZGUgbGFiZWxzCmNvbm5fc2NvcmVfdGVzdCA8LSBmdW5jdGlvbihrbm4sIG8sIG5fcGVybT0xMDAwKXsKICByZWFsX3Njb3JlIDwtIC5jYWxjX2Nvbm5lY3Rpdml0eV9zY29yZShrbm4sIG8pCiAgIyMgUmFuZG9tIHBlcm11dGF0aW9ucwogIHJhbmRfc2NvcmVzIDwtIGMoKQogIGZvciAoaSBpbiAxOm5fcGVybSl7CiAgICByYW5kX2tubiA8LSBrbm4KICAgIFYocmFuZF9rbm4pJG9yZ2FuIDwtIHNhbXBsZShWKGtubikkb3JnYW4pCiAgICByYW5kX3Njb3JlcyA8LSBjKHJhbmRfc2NvcmVzLCAuY2FsY19jb25uZWN0aXZpdHlfc2NvcmUocmFuZF9rbm4sIG8pKSAgIAogIH0KICAKICBwX3ZhbCA8LSBzdW0oYyhyYW5kX3Njb3JlcywgcmVhbF9zY29yZSkgPj0gcmVhbF9zY29yZSkvKG5fcGVybSArIDEpCiAgaWYgKHBfdmFsIDwgMmUtMTYpeyBwX3ZhbCA8LSAyZS0xNn0KICByZXR1cm4oYygnc2NvcmUnPXJlYWxfc2NvcmUsJ3BfdmFsdWUnPXBfdmFsKSkKfQoKIyMgQ2FsY3VsYXRlIGNvbm5lY3Rpdml0eSBzY29yZSArIHNpZ25pZmljYW5jZSB3aXRoIHBlcm11dGF0aW9uIHRlc3QKdGVzdF9jb25uX2dyb3VwIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyLCBuX3Blcm09MTAwMCl7CiAga25uIDwtIGdldF9jdF9LTk5fZ3JhcGgobW9mYV90cmFpbmVkLCBnLCBrPWssIG1pbl9SMiA9IG1pbl9SMikKICB0ZXN0X29yZ3MgPC0gbmFtZXModGFibGUoVihrbm4pJG9yZ2FuKSlbdGFibGUoVihrbm4pJG9yZ2FuKSA+IDJdCiAgcmV0dXJuKHNhcHBseSh0ZXN0X29yZ3MsIGZ1bmN0aW9uKG8pIGNvbm5fc2NvcmVfdGVzdChrbm4sIG8sIG5fcGVybT1uX3Blcm0pKSkKICB9Cgpjb25uZWN0aXZpdHlfdGVzdF9scyA8LSBsYXBwbHkoYWxsX2dyb3VwcywgZnVuY3Rpb24oZykgdGVzdF9jb25uX2dyb3VwKG1vZmFfdHJhaW5lZCwgZykpCmNvbm5lY3Rpdml0eV90ZXN0X2xzIDwtIHNldE5hbWVzKGNvbm5lY3Rpdml0eV90ZXN0X2xzLCBhbGxfZ3JvdXBzKQoKY29ubmVjdGl2aXR5X3Rlc3RfZGYgPC0gaW1hcChjb25uZWN0aXZpdHlfdGVzdF9scywgfiBkYXRhLmZyYW1lKHQoLngpKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJvcmdhbiIpICU+JSBtdXRhdGUoZ3JvdXA9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKGlzX3NpZ25pZiA9IGlmZWxzZShwX3ZhbHVlIDwgMC4wMSwgVFJVRSwgRkFMU0UpKSAKCmNvbm5lY3Rpdml0eV90ZXN0X2RmICU+JQogIGdncGxvdChhZXMob3JnYW4sIGdyb3VwLGZpbGw9bG9nMTAoc2NvcmUpKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlPSJSZWRzIiwgZGlyZWN0aW9uID0gMSkgKwogIGdlb21fdGV4dChkYXRhPS4gJT4lIGZpbHRlcihpc19zaWduaWYpLCBsYWJlbD0iKiIsIHNpemU9NSkKCmBgYApgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpjb25uZWN0aXZpdHlfdGVzdF9kZiAlPiUKICBncm91cF9ieShncm91cCkgJT4lCiAgbXV0YXRlKG1lYW5fdmFsPW1lZGlhbihzY29yZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKC1tZWFuX3ZhbCkgJT4lCiAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPXVuaXF1ZShncm91cCkpKSAlPiUKICBnZ3Bsb3QoYWVzKG9yZ2FuLCBsb2cxcChzY29yZSkpKSArCiAgZ2VvbV9jb2woZmlsbD0iZ3JleSIpICsKICBnZW9tX2NvbChkYXRhPS4gJT4lIGZpbHRlcihpc19zaWduaWYpLCBhZXMoZmlsbD1vcmdhbikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X2dyaWQoZ3JvdXB+LikgKwogIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSkKYGBgCgojIyMjIEV4cHJlc3Npb24gb2YgdG9wIFIyIGZhY3RvcnMKCmBgYHtyfQpnZXRfdG9wX3dlaWdodF9nZW5lcyA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYsIG5fdG9wPTIwLCB3aGljaD0idG9wIil7CiAgd19kZiA8LSBnZXRfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lCiAgICBhcnJhbmdlKHZhbHVlKSAKICBpZiAod2hpY2g9PSJ0b3AiKSB7CiAgICB3X2RmICU+JQogICAgICB0b3BfbihuX3RvcCwgdmFsdWUpICU+JQogICAgICBwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogIH0gZWxzZSBpZiAod2hpY2g9PSJib3R0b20iKXsKICAgIHdfZGYgJT4lCiAgICAgIHRvcF9uKG5fdG9wLCAtdmFsdWUpICU+JQogICAgICBwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogICAgfQp9CgpwbG90X2RhdGFfdG9wX3dlaWdodHMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBjdCwgZiwgbl90b3A9MjAsIHdoaWNoPSJ0b3AiKXsKICBnZW5lcyA8LSBnZXRfdG9wX3dlaWdodF9nZW5lcyhtb2ZhX3RyYWluZWQsIGYsIHdoaWNoPXdoaWNoLCBuX3RvcD1uX3RvcCkKICBkYXRhIDwtIGdldF9kYXRhKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWN0KVtbMV1dW1sxXV1bZ2VuZXMsXQogIAogIHBsX2RmIDwtIHJlc2hhcGUyOjptZWx0KGRhdGEsIHZhcm5hbWVzPWMoImdlbmUiLCAic2FtcGxlIikpICU+JQogICAgbGVmdF9qb2luKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgJT4lCiAgICBhcnJhbmdlKGFnZSkgJT4lCiAgICBtdXRhdGUoc2FtcGxlPWZhY3RvcihzYW1wbGUsIGxldmVscz11bmlxdWUoc2FtcGxlKSkpICU+JQogICAgZ3JvdXBfYnkoZ2VuZSkgJT4lCiAgICBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKQogIHBsX2RmICU+JQogICAgZ2dwbG90KGFlcyhzYW1wbGUsIGdlbmUsIGZpbGw9dmFsdWUpKSArCiAgICBnZW9tX3RpbGUoKSArCiAgICBmYWNldF9ncmlkKC5+b3JnYW4sIHNwYWNlPSJmcmVlIiwgc2NhbGVzPSJmcmVlIikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIoaGlnaD0icmVkIiwgbG93PSJibHVlIiwgbmFtZT0iU2NhbGVkXG5leHByZXNzaW9uIikgKwogICAgeGxhYigiLS0tLWFnZS0tLT4iKSArIHlsYWIoZ2x1ZSgie3doaWNofSB3ZWlnaHQgZ2VuZXMiKSkgKwogICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArCiAgICB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBnZ3RpdGxlKGdsdWUoJ3tjdH0gLSB7Zn0nKSkKfQoKZm9yIChnIGluIGFsbF9ncm91cHMpewogIGZzIDwtIGdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZShtb2ZhX3RyYWluZWQsIGcsIG1pbl9SMj0zKQogIHRvcF9wbG90cyA8LSBsYXBwbHkoZnMsIGZ1bmN0aW9uKHgpIChwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0idG9wIikgKyByZW1vdmVfeF9heGlzKCkpIC8gIAogICAgICAgICAgICAgICAgICAgICAgICBwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0iYm90dG9tIikgKyBnZ3RpdGxlKCIiKQogICkKICBmdWxsX3BsIDwtd3JhcF9wbG90cyh0b3BfcGxvdHMsIG5jb2w9MSkgCiAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L3RvcF9mYWN0b3JzX2V4cHJfe2d9LnBkZiIpLHBsb3Q9ZnVsbF9wbCwgIHdpZHRoPTEyLCBoZWlnaHQgPSA3Kmxlbmd0aCh0b3BfcGxvdHMpKQp9CgpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD04fQpwbG90X2RhdGFfaGVhdG1hcChtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDUsIGdyb3VwcyA9ICJNQVRVUkVfQiIsIHNjYWxlPSJyb3ciLCBhbm5vdGF0aW9uX3NhbXBsZXMgPSBjKCJvcmdhbiIsICJhZ2UiKSwgZmVhdHVyZXMgPSA1MCkKcGxvdF9kYXRhX2hlYXRtYXAobW9mYV90cmFpbmVkLCBmYWN0b3IgPSA1LCBncm91cHMgPSAiQjEiLCBzY2FsZT0icm93IiwgYW5ub3RhdGlvbl9zYW1wbGVzID0gYygib3JnYW4iLCAiYWdlIiksIGZlYXR1cmVzID0gNTApCmBgYAoKCiMjIyBHU0VBCmBgYHtyfQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJNT0ZBZGF0YSIpCmxpYnJhcnkoTU9GQWRhdGEpCnV0aWxzOjpkYXRhKHJlYWN0b21lR1MpCmhlYWQocm93bmFtZXMocmVhY3RvbWVHUykpCgojIyBSZW1vdmUgcm93IHdpdGggTkEKcmVhY3RvbWVHUyA8LSByZWFjdG9tZUdTWyFpcy5uYShyb3duYW1lcyhyZWFjdG9tZUdTKSksXQpgYGAKCmBgYHtyfQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikKaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkKYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KQpkZXRhY2gocGFja2FnZTpFbnNEYi5Ic2FwaWVucy52ODYpCmRldGFjaChwYWNrYWdlOmVuc2VtYmxkYikKCiMgZ2VuZV9uYW1lXzJfaWQgPC0gZnVuY3Rpb24oZ2VuZSl7CiMgICAgcmV0dXJuKGFsbF9nZW5lc1thbGxfZ2VuZXMkZ2VuZV9uYW1lPT1nZW5lLF0kZ2VuZV9pZFsxXSkKIyB9CiMgCiMgZ2VuZV9pZHMgPC0gc2FwcGx5KG1vZmFfdHJhaW5lZEBmZWF0dXJlc19tZXRhZGF0YSRmZWF0dXJlLCBnZW5lX25hbWVfMl9pZCkKIyByb3dEYXRhKHNjZSlbImdlbmVfaWQiXSA8LSBnZW5lX2lkcwojIHJvd0RhdGEoc2NlKVsiZ2VuZV9uYW1lIl0gPC0gcm93bmFtZXMoc2NlKQoKZ2VuZV9uYW1lc19yZWFjdG9tZSA8LSBhbGxfZ2VuZXNbY29sbmFtZXMocmVhY3RvbWVHUyldJGdlbmVfbmFtZQpjb2xuYW1lcyhyZWFjdG9tZUdTKSA8LSBnZW5lX25hbWVzX3JlYWN0b21lCmBgYAoKU3Vic2V0IHRvIGdlbmVzIHRlc3RlZApgYGB7cn0KcmVhY3RvbWVHU191bml2ZXJzZSA8LSByZWFjdG9tZUdTWywgY29sbmFtZXMocmVhY3RvbWVHUykgJWluJSBtb2ZhX3RyYWluZWRAZmVhdHVyZXNfbWV0YWRhdGEkZmVhdHVyZV0KYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD03fQojIEdTRUEgb24gcG9zaXRpdmUgd2VpZ2h0cywgd2l0aCBkZWZhdWx0IG9wdGlvbnMKcmVzLnBvc2l0aXZlIDwtIHJ1bl9lbnJpY2htZW50KG1vZmFfdHJhaW5lZCwKICB2aWV3PSdzY2FsZWRfbG9nY291bnRzJywKICAjIHN0YXRpc3RpY2FsLnRlc3QgPSAnY29yLmFkai5wYXJhbWV0cmljJywKICBmZWF0dXJlLnNldHMgPSByZWFjdG9tZUdTX3VuaXZlcnNlLCAKICBzaWduID0gInBvc2l0aXZlIiwKKQoKIyBHU0VBIG9uIG5lZ2F0aXZlIHdlaWdodHMsIHdpdGggZGVmYXVsdCBvcHRpb25zCnJlcy5uZWdhdGl2ZSA8LSBydW5fZW5yaWNobWVudChtb2ZhX3RyYWluZWQsIAogIHZpZXc9J3NjYWxlZF9sb2djb3VudHMnLAogICMgc3RhdGlzdGljYWwudGVzdCA9ICdjb3IuYWRqLnBhcmFtZXRyaWMnLAogIGZlYXR1cmUuc2V0cyA9IHJlYWN0b21lR1NfdW5pdmVyc2UsIAogIHNpZ24gPSAibmVnYXRpdmUiCikKCgpmb3IgKGYgaW4gMTptb2ZhX3RyYWluZWRAZGltZW5zaW9ucyRLKXsKICBpZiAobWluKHJlcy5wb3NpdGl2ZSRwdmFsLmFkalsscGFzdGUwKCJGYWN0b3IiLCBmKV0pIDwgMC4xKSB7CiAgICBwcmludChwbG90X2VucmljaG1lbnQocmVzLnBvc2l0aXZlLCBmYWN0b3IgPSBmLCBhbHBoYT0wLjEpICsgZ2d0aXRsZSgiUG9zaXRpdmUgd2VpZ2h0cyIpICsKICAgICAgICAgICAgcGxvdF9lbnJpY2htZW50KHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gZiwgYWxwaGE9MC4xKSArIGdndGl0bGUoIk5lZ2F0aXZlIHdlaWdodHMiKSArCiAgICAgICAgICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPXBhc3RlMCgiRmFjdG9yIiwgZikpKQogICAgICB9CiAgfQpgYGAKCmBgYHtyfQpzaWduaWZfcGF0aHdheXMgPC0gcm93bmFtZXMoZGF0YS5mcmFtZShyZXMubmVnYXRpdmUkcHZhbC5hZGopKVtvcmRlcihkYXRhLmZyYW1lKHJlcy5uZWdhdGl2ZSRwdmFsLmFkailbWyJGYWN0b3I4Il1dKVswOjEwXV0KY29sbmFtZXMocmVhY3RvbWVHU191bml2ZXJzZSlbcmVhY3RvbWVHU191bml2ZXJzZVtzaWduaWZfcGF0aHdheXNbNV0sXT09MV0KcGxvdF9lbnJpY2htZW50X2RldGFpbGVkKHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gOCkKYGBgCgotLS0KCiMjIE5vdGVzCgotIEZhY3RvcjIgc2VwYXJhdGVzIEJNIGZyb20gcmVzdAotIEZhY3RvcjU6IGltbWF0dXJlIFZTIG1hdHVyZSBCIGNlbGwgcGhlbm90eXBlLCBzZXBhcmF0ZXMgbWF0dXJlIEIgY2VsbHMgYW5kIEIxIGNlbGxzIGluIGxpdmVyIGFuZCBCTSBmcm9tIHRoZSBvdGhlcnMsIG1vcmUgbWF0dXJlIHBoZW5vdHlwZSAobG93ZXIgZXhwciBvZiBWUFJFQjEgYW5kIGNvLikKCgotLS0KPCEtLSAjIyMgRmFjdG9yIGFubm90YXRpb24gIC0tPgo8IS0tIFNvIGZhciAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgMSAtLT4KPCEtLSBDZWxsIGN5Y2xlIC8gcHJvbGlmZXJhdGlvbiBzaWduYXR1cmUgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDIgLS0+CjwhLS0gRXhwbGFpbnMgdmFyaWF0aW9uIGluIGxhdGUgQiBjZWxsIHN0YWdlcywgcG9zc2libHkgZGlmZmVyZW5jZSBiZXR3ZWVuIEJNIGFuZCBvdGhlciBvcmdhbnM/IC0tPgoKPCEtLSAjIyMjIEZhY3RvciAzIC0tPgo8IS0tIFRoeW11cyBzcGVjaWZpYyBUIGNlbGwgc2lnbmF0dXJlLCBlc3BlY2lhbGx5IGluIGltbWF0dXJlIFQgY2VsbHMuIEludGVyZXN0aW5nbHksIGRpZmZlcmVuY2UgYWxzbyBpbiBCMSBjZWxscywgY291bGQgYmUgc2lnbmFsbGluZyBmcm9tIHRoeW1pYyBtaWNyb2Vudmlyb25tZW50PyAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgNCAtLT4KPCEtLSBWYXJpYXRpb24gd2l0aGluIHByb2dlbml0b3JzLCBhbmQgbG90cyBvZiB2YXJpYW5jZSBleHBsYWluZWQgaW4gQjEgY2VsbHMgdG9vISBTdGVtbmVzcyBtYXJrZXJzIHN1Y2ggYXMgQ0QzNCwgSE9QWC4uLiBFeHBsYWlucyBsb3RzIG9mIHZhcmlhbmNlIGluIFRyZWdzICg3LjY3JSkgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDUgLS0+CjwhLS0gSUxDIHNwZWNpZmljIC0tPgoKPCEtLSAjIyMjIEZhY3RvciA3IC0tPgo8IS0tIENvdWxkIGJlIHNpZ25hdHVyZSBvZiBzcGxlZW4gc3BlY2lmaWMgcHJvZ2VuaXRvcnMsIG9yIHNwbGVlbiBzb3VwIC0tPgoKPCEtLSAjIyMjIEZhY3RvciA4IC0tPgo8IS0tIE1vcmUgY2VsbCBjeWNsZS9wcm9saWZlcmF0aW9uLCBidXQgbG93ZXIgaW4gdGh5bXVzIHNhbXBsZXMsIFRIIHNhbXBsZXMgZXhwcmVzcyBwcm90ZWFzb21lIC0tPgoKPCEtLSAjIyMjIEZhY3RvciAxMCAtLT4KPCEtLSBtYXR1cmUgVlMgcHJvIEIgY2VsbHMgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA3LCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPgo8IS0tICAgbGVmdF9qb2luKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHZhbHVlLCBmaWxsPW9yZ2FuKSkgKyAtLT4KPCEtLSAgIGdlb21faGlzdG9ncmFtKCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KHBST0MpIC0tPgoKPCEtLSBnZXRfb3JnYW5fYXVjIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZiwgbywgZ3JvdXBzKXsgLS0+CjwhLS0gICAgIGRmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFLCBncm91cHMgPSBncm91cHMpICU+JSAtLT4KPCEtLSAgICAgbGVmdF9qb2luKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhKSAtLT4KCjwhLS0gICBjYXQgPC0gYXMubnVtZXJpYyhkZiRvcmdhbj09bykgLS0+CjwhLS0gICBwcmVkIDwtIGRmJHZhbHVlIC0tPgo8IS0tICAgaWYgKHN1bShjYXQpID4gMCkgeyAtLT4KPCEtLSAgICAgcm9jX29iaiA8LSByb2MoY2F0LCBwcmVkKSAtLT4KPCEtLSAgICAgYXVjIDwtIGF1Yyhyb2Nfb2JqKSAtLT4KPCEtLSAgICAgcmV0dXJuKGFzLnZlY3RvcihhdWMpKSAtLT4KPCEtLSAgICAgfSAtLT4KPCEtLSB9IC0tPgoKPCEtLSB0b3BfZ3JfZGYgPC0gbGFwcGx5KDE6MTksIGZ1bmN0aW9uKGYpIGRhdGEuZnJhbWUodG9wX2dyb3VwPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGYpLCBmYWN0b3I9ZikpICU+JSAtLT4KPCEtLSAgIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAgLS0+Cgo8IS0tIG9yZyA9ICJCTSIgLS0+CjwhLS0gQVVDX29yZyA8LSBzYXBwbHkoMTpucm93KHRvcF9ncl9kZiksIGZ1bmN0aW9uKGkpeyAtLT4KPCEtLSAgIGdldF9vcmdhbl9hdWMobW9mYV90cmFpbmVkLCAgLS0+CjwhLS0gICAgICAgICAgICAgICAgIG89b3JnLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgZj10b3BfZ3JfZGYkZmFjdG9yW2ldLCAgLS0+CjwhLS0gICAgICAgICAgICAgICAgIGdyb3VwcyA9IHRvcF9ncl9kZiR0b3BfZ3JvdXBbaV0pfSAtLT4KPCEtLSAgICkgLS0+CjwhLS0gQVVDX29yZ1tzYXBwbHkoQVVDX29yZywgaXMubnVsbCldIDwtIE5BIC0tPgo8IS0tIHRvcF9ncl9kZltbIkFVQ19vcmciXV0gPC0gdW5saXN0KEFVQ19vcmcpIC0tPgoKPCEtLSBnZ3Bsb3QodG9wX2dyX2RmLCBhZXMoZmFjdG9yLCBmaWxsPUFVQ19vcmcsIHRvcF9ncm91cCkpICArIC0tPgo8IS0tICAgZ2VvbV90aWxlKCkgKyAtLT4KPCEtLSAgIGdlb21fdGV4dChhZXMobGFiZWw9cm91bmQoQVVDX29yZywgMikpKSArIC0tPgo8IS0tICAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSAtLT4KCjwhLS0gYGBgIC0tPgoKPCEtLSAtLS0gLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikgLS0+CjwhLS0gaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkgLS0+CjwhLS0gYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KSAtLT4KCjwhLS0gZ2VuZV9uYW1lXzJfaWQgPC0gZnVuY3Rpb24oZ2VuZSl7IC0tPgo8IS0tICAgIHJldHVybihhbGxfZ2VuZXNbYWxsX2dlbmVzJGdlbmVfbmFtZT09Z2VuZSxdJGdlbmVfaWRbMV0pIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGdlbmVfaWRzIDwtIHNhcHBseShyb3duYW1lcyhzY2UpLCBnZW5lX25hbWVfMl9pZCkgLS0+CjwhLS0gcm93RGF0YShzY2UpWyJnZW5lX2lkIl0gPC0gZ2VuZV9pZHMgLS0+CjwhLS0gcm93RGF0YShzY2UpWyJnZW5lX25hbWUiXSA8LSByb3duYW1lcyhzY2UpIC0tPgoKPCEtLSByb3duYW1lcyhzY2UpIDwtIHJvd0RhdGEoc2NlKVtbImdlbmVfaWQiXV0gLS0+Cgo8IS0tIGFzc2lnbm1lbnRzIDwtIGN5Y2xvbmUoc2NlLCBoZy5wYWlycywgYXNzYXkudHlwZT0ibG9nY291bnRzIikgLS0+Cgo8IS0tICMjIEFkZCAicGhhc2UiIGFzc2lnbm1lbnRzIHRvIG1vZmEgLS0+CjwhLS0gc2NlJGNlbGxjeWNsZV9waGFzZSA8LSBhc3NpZ25tZW50cyRwaGFzZXMgLS0+CjwhLS0gc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpICA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNlbGxjeWNsZV9waGFzZT1zY2VbLG1hdGNoKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSRzYW1wbGUsIGNvbG5hbWVzKHNjZSkpXSRjZWxsY3ljbGVfcGhhc2UpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3RfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSAxLCBjb2xvcl9ieSA9ICJjZWxsY3ljbGVfcGhhc2UiKSAtLT4KPCEtLSBgYGAgLS0+CgoKPCEtLSA8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9NX0gLS0+IC0tPgo8IS0tIDwhLS0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gMywgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShvcmdhbiA9IHNhcHBseShzdHJfc3BsaXQoc2FtcGxlLCAiLSIpLCBmdW5jdGlvbih4KSB4W2xlbmd0aCh4KS0zXSkpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGdyb3VwX2J5KGdyb3VwKSAlPiUgLS0+IC0tPgo8IS0tIDwhLS0gICBtdXRhdGUoZ3JfbWVhbiA9IG1lZGlhbih2YWx1ZSkpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIHVuZ3JvdXAoKSAlPiUgLS0+IC0tPgo8IS0tIDwhLS0gICBhcnJhbmdlKGdyX21lYW4pICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgZ2dwbG90KGFlcyhvcmdhbiwgdmFsdWUsIGNvbG9yPW9yZ2FuKSkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGdlb21fYm94cGxvdCgpICsgLS0+IC0tPgo8IS0tIDwhLS0gICBnZW9tX2ppdHRlcigpICsgLS0+IC0tPgo8IS0tIDwhLS0gICAjIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPTIpICsgLS0+IC0tPgo8IS0tIDwhLS0gICBjb29yZF9mbGlwKCkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGZhY2V0X3dyYXAoLn5ncm91cCwgc2NhbGVzID0gImZyZWVfeCIpIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgIGdyb3VwX2J5ID0gImdyb3VwIiwgIGRvdF9zaXplID0gMC44LCBhZGRfYm94cGxvdCA9IFRSVUUsIGRvZGdlID0gVFJVRSkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGNvb3JkX2ZsaXAoKSAtLT4gLS0+CjwhLS0gPCEtLSBgYGAgLS0+IC0tPgoKCjwhLS0gIyMgR28gYnkgY2VsbHR5cGUgaW5zdGVhZCBvZiBmYWN0b3IgLS0+Cgo8IS0tICMjIyBEQzEgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUgLS0+CjwhLS0gICBmaWx0ZXIoZ3JvdXA9PSJEQzEiKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKGZhY3RvciwgdmFsdWUpKSArIGdlb21fY29sKCkgKyAtLT4KPCEtLSAgIGNvb3JkX2ZsaXAoKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChncm91cH4uLCBuY29sID0gNiwgc2NhbGVzID0gImZyZWVfeCIpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGMoMiw0KSwgY29sb3JfYnkgPSAib3JnYW4iLCBncm91cHMgPSAiREMxIikgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTR9IC0tPgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGMoNCksIGNvbG9yX2J5ID0gIm9yZ2FuIiwgZ3JvdXBfYnkgPSAib3JnYW4iLCBncm91cHMgPSAiREMxIikgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gNCwgZ3JvdXBfYnkgPSAiZ3JvdXAiLCBjb2xvcl9ieSA9ICJvcmdhbiIsIGRvdF9zaXplID0gMC44LCBhZGRfYm94cGxvdCA9IFRSVUUsIGRvZGdlID0gVFJVRSkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gNCwgbmZlYXR1cmVzID0gMzApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9kYXRhX3NjYXR0ZXIobW9mYV90cmFpbmVkLCBmYWN0b3IgPSA0LCBncm91cHM9IkRDMSIsIGNvbG9yPSJvcmdhbiIsIGZlYXR1cmVzPSJITEEtRFJBIikgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSAjIyBFeHBsb3JlIGJ5IGZhY3RvciAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3IgPSAzKSAtLT4KPCEtLSBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3IgPSAzLCBuZmVhdHVyZXMgPSAyMCkgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gIyMgRmluZCBmYWN0b3JzIHRoYXQgZGlzY3JpbWluYXRlIGJldHdlZW4gb3JnYW5zIC0tPgoKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF9vcmdhbl9BVUMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmLCBncil7IC0tPgo8IS0tICAgZl9kZiA8LSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBncm91cHMgPSBnciwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4KPCEtLSAgICAgIyBncm91cF9ieShncm91cCkgJT4lIC0tPgo8IS0tICAgICAjIG11dGF0ZSh2YWx1ZT1zY2FsZSh2YWx1ZSkpICU+JSAtLT4KPCEtLSAgICAgIyB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgICBtdXRhdGUob3JnYW4gPSBzYXBwbHkoc3RyX3NwbGl0KHNhbXBsZSwgIi0iKSwgZnVuY3Rpb24oeCkgeFtsZW5ndGgoeCktM10pKSAgLS0+CjwhLS0gICBvcmdhbnMgPC0gdW5pcXVlKGZfZGYkb3JnYW4pIC0tPgo8IS0tICAgc3VwcHJlc3NXYXJuaW5ncyhzdXBwcmVzc01lc3NhZ2VzKHtvcmdfYXVjIDwtIHNhcHBseShvcmdhbnMsIGZ1bmN0aW9uKG9yZykgcm9jKGFzLm51bWVyaWMoZl9kZiRvcmdhbj09b3JnKSwgZl9kZiR2YWx1ZSkkYXVjKX0pKSAtLT4KPCEtLSAgIGFsbF9vcmdhbnMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSRvcmdhbikpIC0tPgo8IS0tICAgb3JnX2F1YyA8LSBzZXROYW1lcyhvcmdfYXVjW2FsbF9vcmdhbnNdLCBhbGxfb3JnYW5zKSAtLT4KPCEtLSAgIHJldHVybihvcmdfYXVjKSAtLT4KPCEtLSB9IC0tPgoKPCEtLSBhbGxfb3JnYW5zIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUobW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEkb3JnYW4pKSAtLT4KPCEtLSBhbGxfZ3JvdXBzIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUobW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEkZ3JvdXApKSAtLT4KCjwhLS0gIyMgTWFzayBpZiB0b28gbGl0dGxlIHNhbXBsZXMgLS0+CjwhLS0gbl9zYW1wbGVzX21hdCA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkob3JnYW4sIGdyb3VwKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lIC0tPgo8IS0tICAgcGl2b3Rfd2lkZXIoaWRfY29scz1jKGdyb3VwKSwgbmFtZXNfZnJvbT0ib3JnYW4iLCB2YWx1ZXNfZnJvbT0ibl9zYW1wbGVzIiwgdmFsdWVzX2ZpbGw9MCkgJT4lIC0tPgo8IS0tICAgY29sdW1uX3RvX3Jvd25hbWVzKCJncm91cCIpICU+JSAtLT4KPCEtLSAgIGFzLm1hdHJpeCgpIC0tPgoKPCEtLSBtYXNrX3BhaXJzIDwtIHQobl9zYW1wbGVzX21hdCA8IDMpIC0tPgoKPCEtLSBBVUNfbWF0IDwtIHNhcHBseShhbGxfZ3JvdXBzLCBmdW5jdGlvbihnKSBnZXRfb3JnYW5fQVVDKG1vZmFfdHJhaW5lZCwgZj0xMCwgZ3I9ZykpIC0tPgo8IS0tIEFVQ19tYXRbbWFza19wYWlyc1tyb3duYW1lcyhBVUNfbWF0KSwgY29sbmFtZXMoQVVDX21hdCldXSA8LSBOQSAtLT4KCjwhLS0gQVVDX3RocmVzaCA9IDAuOCAtLT4KPCEtLSByZXNoYXBlMjo6bWVsdChBVUNfbWF0LCB2YXJuYW1lcz1jKCJvcmdhbiIsICJncm91cCIpLCB2YWx1ZS5uYW1lPSJBVUMiKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKG9yZ2FuLCBncm91cCkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGFlcyhzaXplPUFVQywgY29sb3I9QVVDKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoQVVDID4gQVVDX3RocmVzaCksIHNoYXBlPTgsIHNpemU9Mixjb2xvcj0id2hpdGUiKSArIC0tPgo8IS0tICAgc2NhbGVfc2l6ZShsaW1pdHMgPSBjKDAuNSwxKSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDUsICJSZWRzIikpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9NH0gLS0+CjwhLS0gbGlicmFyeShwYXRjaHdvcmspIC0tPgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDUsIGdyb3VwX2J5ID0gImdyb3VwIiwgY29sb3JfYnkgPSAib3JnYW4iLCBkb2RnZSA9IFRSVUUsIGFkZF9ib3hwbG90ID0gVFJVRSkgIC0tPgoKPCEtLSAgIHBsb3RfbGF5b3V0KGd1aWRlcz0iY29sbGVjdCIpIC0tPgoKPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA1LCBuZmVhdHVyZXMgPSAzMCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2RhdGFfaGVhdG1hcChtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDUsIHNob3dfY29sbmFtZXM9RkFMU0UpIC0tPgo8IS0tIGBgYCAtLT4KCgoKPCEtLSAjIE1vZGVsIDMgLSAgTUVGSVNUTyAgLS0+Cgo8IS0tIEFkZCB0aW1lIGFzIGNvdmFyaWF0ZSB0byBydW4gTUVGSVNUTyAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tICMjIFZlY3RvciBmb3IgdGltZSBhc3NpZ25tZW50IC0tPgo8IS0tIHRpbWVzIDwtIGRpc3RpbmN0KGRhdGEuZnJhbWUoYWdlPXNjZSRhZ2UsIG5ld19zYW1wbGUpKSAlPiUgLS0+CjwhLS0gICBjb2x1bW5fdG9fcm93bmFtZXMoJ25ld19zYW1wbGUnKSAlPiUgLS0+CjwhLS0gICAuW3NhbXBsZV9uYW1lc191bmlxdWUsXSAtLT4KCjwhLS0gc2FtcGxlc19tZXRhZGF0YShtb2ZhKVtbInRpbWUiXV0gPC0gdGltZXMgLS0+Cgo8IS0tIG1vZmEgPC0gc2V0X2NvdmFyaWF0ZXMobW9mYSwgY292YXJpYXRlcyA9ICJ0aW1lIikgLS0+CjwhLS0gbW9mYSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0xMH0gLS0+CjwhLS0gZ2dfaW5wdXQgPC0gcGxvdF9kYXRhX292ZXJ2aWV3KG1vZmEsIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvdmFyaWF0ZSA9IFRSVUUsIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2RpbWVuc2lvbnMgPSBUUlVFKSAgLS0+CjwhLS0gZ2dfaW5wdXQgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSA8IS0tIEtlZXAgZ3JvdXBzIHRoYXQgc3BhbiBtdWx0aXBsZSB2aWV3cyAtLT4gLS0+CjwhLS0gPCEtLSBgYGB7cn0gLS0+IC0tPgo8IS0tIDwhLS0gZ3Jfc2FtcGxlcyA8LSBzcGxpdChzYW1wbGVzX21ldGFkYXRhKG1vZmEpJHNhbXBsZSwgc2FtcGxlc19tZXRhZGF0YShtb2ZhKSRncm91cCkgLS0+IC0tPgo8IS0tIDwhLS0gYWxsKGlzLm5hKGRhdGEkQk1bLGdyX3NhbXBsZXMkQmFzb3BoaWxdKSkgLS0+IC0tPgo8IS0tIDwhLS0gbGFwcGx5KHVuaXF1ZShzYW1wbGVzX21ldGFkYXRhKG1vZmEpW1siZ3JvdXAiXV0pLCBmdW5jdGlvbih4KSBkYXRhJEJNW10pIC0tPiAtLT4KCgo8IS0tIDwhLS0gbW9mYUBkYXRhIC0tPiAtLT4KPCEtLSA8IS0tIHN1YnNlKG1vZmEpWyxzYW1wbGVzX21ldGFkYXRhKG1vZmEpW1siZ3JvdXAiXV0gPT0gIkJhc29waGlsIl0gLS0+IC0tPgo8IS0tIDwhLS0gYGBgIC0tPiAtLT4KCjwhLS0gUHJlcGFyZSA0IHRyYWluaW5nIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gZGF0YV9vcHRzIDwtIGdldF9kZWZhdWx0X2RhdGFfb3B0aW9ucyhtb2ZhKSAtLT4KCjwhLS0gbW9kZWxfb3B0cyA8LSBnZXRfZGVmYXVsdF9tb2RlbF9vcHRpb25zKG1vZmEpIC0tPgo8IS0tIG1vZGVsX29wdHMkbnVtX2ZhY3RvcnMgPC0gMTAgLS0+Cgo8IS0tIHRyYWluX29wdHMgPC0gZ2V0X2RlZmF1bHRfdHJhaW5pbmdfb3B0aW9ucyhtb2ZhKSAtLT4KPCEtLSB0cmFpbl9vcHRzJHNlZWQgPC0gMjAyMCAtLT4KPCEtLSB0cmFpbl9vcHRzJGNvbnZlcmdlbmNlX21vZGUgPC0gImZhc3QiICMgdXNlICJmYXN0IiBmb3IgZmFzdGVyIHRyYWluaW5nIC0tPgoKPCEtLSBtZWZpc3RvX29wdHMgPC0gZ2V0X2RlZmF1bHRfbWVmaXN0b19vcHRpb25zKG1vZmEpIC0tPgo8IS0tIG1lZmlzdG9fb3B0cyR3YXJwaW5nIDwtIEZBTFNFIC0tPgo8IS0tICMgbWVmaXN0b19vcHRzJHNwYXJzZUdQIDwtIFRSVUUgLS0+Cgo8IS0tIG1vZmEgPC0gcHJlcGFyZV9tb2ZhKCAtLT4KPCEtLSAgIG9iamVjdCA9IG1vZmEsIC0tPgo8IS0tICAgZGF0YV9vcHRpb25zID0gZGF0YV9vcHRzLCAtLT4KPCEtLSAgIG1vZGVsX29wdGlvbnMgPSBtb2RlbF9vcHRzLCAtLT4KPCEtLSAgIHRyYWluaW5nX29wdGlvbnMgPSB0cmFpbl9vcHRzLCAtLT4KPCEtLSAgIG1lZmlzdG9fb3B0aW9ucyA9IG1lZmlzdG9fb3B0cyAtLT4KPCEtLSApICAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIFRyYWluIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gb3V0ZmlsZSA8LSAiL25mcy90ZWFtMjA1L2VkNi9kYXRhL0ZldGFsX2ltbXVuZS9teWVsb2lkX21lZmlzdG9fbW9kZWwuaGRmNSIgLS0+CjwhLS0gbW9mYV90cmFpbmVkIDwtIHJ1bl9tb2ZhKG1vZmEsIG91dGZpbGUgPSBvdXRmaWxlKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIExvYWQgdHJhaW5lZCBtb2RlbCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gbW9mYV90cmFpbmVkIDwtIGxvYWRfbW9kZWwob3V0ZmlsZSwgbG9hZF9pbnRlcnBvbF9aID0gVFJVRSkgLS0+CjwhLS0gYGBgIC0tPgoK